> -----Original Message-----
> From: Martin Sebor [mailto:[EMAIL PROTECTED]
> Sent: Friday, July 21, 2006 4:00 AM
> To: [email protected]
> Subject: RE: string methods thread safety
>
> Could you please post your proposed ChangeLog entry for all
> nontrivial changes? It will make it easier for me to
> understand what's going on.
> Thanks! :)
>

  The test exercising string thread safety (21.string.mt.cpp).
The test file and 21.string.*.diff are attached.
The test requires the files rw_process.h and process.cpp (I have sent
this files in the separate letter).

  At first the test checks were option --overload_id=xxx is specified.
If the option is not specified it executes the self copy with
the all own and the three additional parameters:
1) --overload_id={one of the exercising StringIds::OverloadId};
2) --no-stdout to prevent the output of the copy process to the
test driver console;
3) --no-UserAlloc to prevent UserAlloc allocator using
due to SharedAlloc (used by UserAlloc) is not a thread safe.

  The child copy is executed for each overload id from overloads array.
When the parameter --overload_id=xxx is specified the test performs the thread safety test of the specified overload. If shared string is modified after the test, the global variable _rw_status is set to 1 (to detect the string modification I had to slightly modify the StringState::assert_equal() method). This variable is a return code of the child process to transfer the success of the overload test to the driver (parent process). I used the global variable because the callback for rw_run_string_test() returns void.

For the range overloads (append_range, assign_range, insert_iter_range, replace_iter_iter_range) the basic_string<>::const_iterator overloads tested only. I am not sure that it's enough, but I can add testing of the other overloads later.

When the parent process receives the return code it checks this by rw_assert(). To print the method signature I used _rw_setvars from 21.strings.cpp. For this I renamed it to rw_setvars due to naming conventions. I don't like it too, but I have no the better idea yet.

> The only thing I'm not quite happy with so far is making
> _rw_setvars extern. According to the (still unwritten, my
> bad) naming convention, names that start with _rw_ have
> internal linkage, and shouldn't be exposed to the world
> (i.e., to the tests or other objects). If it turns out that
> the function is helpful elsewhere besides the TU where it's
> declared static we should rename it (and drop the leading
> underscore). The burden that should come with doing it is
> writing a test for the function.

Farid.
/***************************************************************************
 *
 * 21.string.mt.cpp - string test exercising string methods thread safety
 *
 * $Id: 21.string.mt.cpp $
 *
 ***************************************************************************
 *
 * 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.
 *
 **************************************************************************/

#include <string>           // for string
#include <istream>          // for istream
#include <locale>           // for locale

#include <stddef.h>         // for size_t
#include <stdlib.h>         // for malloc()
#include <stdio.h>          // for sprintf()
#include <string.h>         // for memcpy()
#include <limits.h>         // for CHARBIT

#include <21.strings.h>     // for StringMembers
#include <driver.h>         // for rw_test()
#include <rw_process.h>     // for rw_process_create(), rw_process_join()
#include <rw_thread.h>      // for rw_thread_pool()
#include <rw_allocator.h>   // for UserAlloc
#include <cmdopt.h>         // for rw_setops(), rw_runopts()
#include <rw_printf.h>      // for rw_fprintf()
#include <rw_ctype.h>       // for UserCtype
#include <rw_streambuf.h>   // for MyStreambuf

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

#define MAX_THREADS      32
#define MAX_LOOPS     10000

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

int rw_opt_overload_id = -1;
int rw_opt_nthreads    = 4;
int rw_opt_nloops      = MAX_LOOPS;
int rw_opt_dummy;

static int _rw_status  = 0;

static char param2[] = "--no-stdout";
static char param3[] = "--no-UserAlloc";

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

static const StringIds::OverloadId overloads [] = {

#undef TEST
#define TEST(sig) StringIds:: sig

    TEST (append_cptr),
    TEST (append_cstr),
    TEST (append_cptr_size),
    TEST (append_cstr_size_size),
    TEST (append_size_val),
    TEST (append_range),

    TEST (assign_cptr),
    TEST (assign_cstr),
    TEST (assign_cptr_size),
    TEST (assign_cstr_size_size),
    TEST (assign_size_val),
    TEST (assign_range),

    TEST (erase_void),
    TEST (erase_size),
    TEST (erase_size_size),
    TEST (erase_iter),
    TEST (erase_iter_iter),

    TEST (insert_size_cptr),
    TEST (insert_size_cstr),
    TEST (insert_size_cptr_size),
    TEST (insert_size_cstr_size_size),
    TEST (insert_size_size_val),
    TEST (insert_iter_val),
    TEST (insert_iter_size_val),
    TEST (insert_iter_range),

    TEST (replace_size_size_cptr),
    TEST (replace_size_size_cstr),
    TEST (replace_size_size_cptr_size),
    TEST (replace_size_size_cstr_size_size),
    TEST (replace_size_size_size_val),
    TEST (replace_iter_iter_cptr),
    TEST (replace_iter_iter_cstr),
    TEST (replace_iter_iter_cptr_size),
    TEST (replace_iter_iter_size_val),
    TEST (replace_iter_iter_range),

    TEST (op_plus_eq_cptr),
    TEST (op_plus_eq_cstr),
    TEST (op_plus_eq_val),

    TEST (substr_void),
    TEST (substr_size),
    TEST (substr_size_size),

    TEST (op_index_size),
    TEST (at_size),

    TEST (swap_str),

    TEST (push_back_val),

    TEST (resize_size_val),
    TEST (resize_size),

    TEST (reserve_size),
    TEST (reserve_void),

    TEST (clear_void),

    TEST (begin_void),
    TEST (end_void),

    TEST (rbegin_void),
    TEST (rend_void),

    TEST (extractor_istream_str),
    TEST (getline_istream_str),
    TEST (getline_istream_str_val)
};

static const size_t test_count = sizeof (overloads) / sizeof (*overloads);

static const char param_name[] = "--overload_id=";
static const size_t param_len  = sizeof (param_name) - 1;

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

extern void
rw_setvars (const StringFunc     &func,
             const StringTestCase *pcase);

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

template <class charT, class Traits, class Allocator>
struct MtTestCaseDataT
{
    typedef std::basic_string <charT, Traits, Allocator> String;
    typedef StringTestCaseData<charT>                    TestCaseData;

    const String         &str_;
    const TestCaseData   &tdata_;
};

template <class charT, class Traits, class Allocator>
static void*
thread_func (void *th_arg)
{
    typedef MtTestCaseDataT<charT, Traits, Allocator> MtTestCaseData;
    typedef typename MtTestCaseData::String           String;
    typedef typename String::iterator                 StringIter;
    typedef typename MtTestCaseData::TestCaseData     TestCaseData;

    typedef MyStreambuf<charT, Traits>                Streambuf;
    typedef std::basic_ios<charT, Traits>             BasicIos;
    typedef std::basic_istream<charT, Traits>         Istream;

    const MtTestCaseData &mt_tdata =
        *_RWSTD_STATIC_CAST (MtTestCaseData*, th_arg);

    const TestCaseData   &tdata    = mt_tdata.tdata_;
    const StringFunc     &func     = tdata.func_;
    const StringTestCase &tcase    = tdata.tcase_;

    String arg (tdata.arg_, tdata.arglen_);

    const charT arg_val = (make_char (char (tcase.val), (charT*)0));

    for (size_t i = 0; i != size_t (rw_opt_nloops); ++i) {

        String str (mt_tdata.str_);

        const charT* const arg_ptr = tcase.arg ? arg.c_str () : str.c_str ();
        String&            arg_str = tcase.arg ? arg : str;

        const size_t size1 = str.length ();
        const size_t off1 =
            size_t (tcase.off) < size1 ? size_t (tcase.off) : size1;
        const size_t ext1 =
            off1 + tcase.size < size1 ? tcase.size : size1 - off1;

        // create a pair of iterators into the string object being
        // modified (used only by the iterator overloads)
        const StringIter it_first (str.begin () + off1);
        const StringIter it_last  (it_first + ext1);

        switch (func.which_) {

        case StringIds::append_cptr:
            str.append (arg_ptr);
            break;
        case StringIds::append_cstr:
            str.append (arg_str);
            break;
        case StringIds::append_cptr_size:
            str.append (arg_ptr, tcase.size);
            break;
        case StringIds::append_cstr_size_size:
            str.append (arg_str, tcase.off2, tcase.size2);
            break;
        case StringIds::append_size_val:
            str.append (tcase.size, arg_val);
            break;
        case StringIds::append_range:
            str.append (it_first, it_last);
            break;

        case StringIds::assign_cptr:
            str.assign (arg_ptr);
            break;
        case StringIds::assign_cstr:
            str.assign (arg_str);
            break;
        case StringIds::assign_cptr_size:
            str.assign (arg_ptr, tcase.size);
            break;
        case StringIds::assign_cstr_size_size:
            str.assign (arg_str, tcase.off2, tcase.size2);
            break;
        case StringIds::assign_size_val:
            str.assign (tcase.size, arg_val);
            break;
        case StringIds::assign_range:
            str.assign (it_first, it_last);
            break;

        case StringIds::erase_void:
            str.erase ();
            break;
        case StringIds::erase_size:
            str.erase (tcase.off);
            break;
        case StringIds::erase_size_size:
            str.erase (tcase.off, tcase.size);
            break;
        case StringIds::erase_iter:
            str.erase (it_first);
            break;
        case StringIds::erase_iter_iter:
            str.erase (it_first, it_last);
            break;

        case StringIds::insert_size_cptr:
            str.insert (tcase.off, arg_ptr);
            break;
        case StringIds::insert_size_cstr:
            str.insert (tcase.off, arg_str);
            break;
        case StringIds::insert_size_cptr_size:
            str.insert (tcase.off, arg_ptr, tcase.size2);
            break;
        case StringIds::insert_size_cstr_size_size:
            str.insert (tcase.off, arg_str, tcase.off2, tcase.size2);
            break;
        case StringIds::insert_size_size_val:
            str.insert (tcase.off, tcase.size2, arg_val);
            break;
        case StringIds::insert_iter_val:
            str.insert (it_first, arg_val);
            break;
        case StringIds::insert_iter_size_val:
            str.insert (it_first, tcase.size2, arg_val);
            break;
        case StringIds::insert_iter_range:
            str.insert (it_first, it_first, it_last);
            break;

        case StringIds::replace_size_size_cptr:
            str.replace (tcase.off, tcase.size, arg_ptr);
            break;
        case StringIds::replace_size_size_cstr:
            str.replace (tcase.off, tcase.size, arg_str);
            break;
        case StringIds::replace_size_size_cptr_size:
            str.replace (tcase.off, tcase.size, arg_ptr, tcase.size);
            break;
        case StringIds::replace_size_size_cstr_size_size:
            str.replace (tcase.off, tcase.size, arg_str, tcase.off2, 
tcase.size);
            break;
        case StringIds::replace_size_size_size_val:
            str.replace (tcase.off, tcase.size, tcase.size2, arg_val);
            break;
        case StringIds::replace_iter_iter_cptr:
            str.replace (it_first, it_last, arg_ptr);
            break;
        case StringIds::replace_iter_iter_cstr:
            str.replace (it_first, it_last, arg_str);
            break;
        case StringIds::replace_iter_iter_cptr_size:
            str.replace (it_first, it_last, arg_ptr, tcase.size2);
            break;
        case StringIds::replace_iter_iter_size_val:
            str.replace (it_first, it_last, tcase.size2, arg_val);
            break;
        case StringIds::replace_iter_iter_range:
            str.replace (it_first, it_first + 1, it_first, it_last);
            break;

        case StringIds::op_plus_eq_cptr:
            str.operator+= (arg_ptr);
            break;
        case StringIds::op_plus_eq_cstr:
            str.operator+= (arg_str);
            break;
        case StringIds::op_plus_eq_val:
            str.operator+= (arg_val);
            break;

        case StringIds::substr_void:
            str.substr ();
            break;
        case StringIds::substr_size:
            str.substr (tcase.off);
            break;
        case StringIds::substr_size_size:
            str.substr (tcase.off, tcase.size);
            break;

        case StringIds::op_index_size:
            str [tcase.off] = arg_val;
            break;
        case StringIds::at_size:
            str.at (tcase.off) = arg_val;
            break;

        case StringIds::swap_str:
            str.swap (arg_str);
            break;

        case StringIds::push_back_val:
            str.push_back (arg_val);
            break;

        case StringIds::resize_size_val:
            str.resize (tcase.size, arg_val);
            break;
        case StringIds::resize_size:
            str.resize (tcase.size);
           break;

        case StringIds::reserve_size:
            str.reserve (tcase.size);
            break;
        case StringIds::reserve_void:
            str.reserve ();
            break;

        case StringIds::clear_void:
            str.clear ();
            break;

        case StringIds::begin_void:
            *str.begin () = arg_val;
            break;
        case StringIds::end_void:
            *(str.end () - 1) = arg_val;
            break;

        case StringIds::rbegin_void:
            *str.rbegin () = arg_val;
            break;
        case StringIds::rend_void:
            *(str.rend () - 1) = arg_val;
            break;

        case StringIds::extractor_istream_str:
        case StringIds::getline_istream_str:
        case StringIds::getline_istream_str_val:
            {
                const UserCtype<charT> ctyp (1);
                Streambuf inbuf (tcase.arg, tcase.arg_len, -1, -1);
                Istream is (&inbuf);
                is.imbue (std::locale (is.getloc (), &ctyp));

                if (StringIds::extractor_istream_str == func.which_)
                    is >> str;
                else if (StringIds::getline_istream_str == func.which_)
                    std::getline (is, str);
                else if (StringIds::getline_istream_str_val == func.which_)
                    std::getline (is, str, arg_val);
            }
            break;

        default:
            RW_ASSERT (!"test logic error: unknown mt overload");
        }
    }

    return 0;
}

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

template <class charT, class Traits, class Allocator>
void test_mt (charT*, Traits*, Allocator*, 
              const StringTestCaseData<charT> &tdata)
{
    typedef std::basic_string <charT, Traits, Allocator> String;
    typedef MtTestCaseDataT<charT, Traits, Allocator>    MtTestCaseData;

    if (  StringIds::None          != tdata.func_.iter_id_
       && StringIds::ConstIterator != tdata.func_.iter_id_ )
    {
        // for range methods overloads test the StringIds::ConstIterator only
        return;
    }

    const String cstr (tdata.str_, tdata.strlen_);

    // save the state of the const string object before the call
    // to any (changes to the state of the object after a call)
    const StringState cstr_state (rw_get_string_state (cstr));

    MtTestCaseData tdata_ = { cstr, tdata };

    void* args [MAX_THREADS];
    for (size_t i = 0; i < sizeof (args) / sizeof (*args); ++i)
        args [i] = _RWSTD_STATIC_CAST(void*, &tdata_);

    // create and start a pool of threads and wait for them to finish
    rw_thread_pool (0, size_t (rw_opt_nthreads),
        0, &thread_func<charT, Traits, Allocator>, args);

    // verify that const string object was not modified
    int res = cstr_state.assert_equal (rw_get_string_state (cstr),
        __LINE__, tdata.tcase_.line, "call");

    if (0 == res)
        _rw_status = 1;
}

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

DEFINE_STRING_TEST_FUNCTIONS (test_mt);

static int
run_test (int argc, char** argv)
{
    RW_ASSERT (0 < argc);

    rw_info (0, 0, 0, "running %d thread%{?}s%{;}, %zu iteration%{?}s%{;} each",
             rw_opt_nthreads, 1 != rw_opt_nthreads,
             rw_opt_nloops, 1 != rw_opt_nloops);

    // allocate argv_ buffer + additional 4 elements:
    // 1) --overload_id=xxx
    // 2) --no-stdout
    // 3) --no-UserAlloc
    // 4) terminating NUL
    char ** argv_ = _RWSTD_STATIC_CAST (char**,
        malloc ((argc + 4) * sizeof (*argv_)));
    if (!argv_)
        return 1;

    // path
    argv_ [0] = argv [0];
    // leave the argv_[1] .. argv_[3] to init later
    memcpy (argv_ + 4, argv + 1, (argc - 1) * sizeof (*argv_));
    argv_ [2] = param2;
    argv_ [3] = param3;
    // terminating NUL 
    argv_ [argc + 3] = 0;

    int fails = 0;

    char overload_id [sizeof (param_name) +
        (CHAR_BIT * sizeof (unsigned)) * 301L / 1000 + 1];

    memcpy (overload_id, param_name, param_len);

    char* const pos = overload_id + param_len;

    for (size_t i = 0; i < test_count; ++i) {

        typedef StringIds Ids;

        const Ids::OverloadId which = overloads [i];

        sprintf (pos, "%u", unsigned (which));
        argv_ [1] = overload_id;

        const bool is_range =
               Ids::append_range            == which
            || Ids::assign_range            == which
            || Ids::insert_iter_range       == which
            || Ids::replace_iter_iter_range == which;

        const StringFunc func = {
            Ids::Char,
            Ids::DefaultTraits,
            Ids::DefaultAlloc,
            is_range ? Ids::ConstIterator : Ids::None,
            which
        };

        // sets the {CLASS}, {FUNCSIG} environment variables
        rw_setvars (func, 0);

        // determine whether the function is a member function
        const bool is_member = 0 != (Ids::bit_member & which);

        rw_info (0, 0, 0,
            "exercising the %{?}%{$CLASS}::%{;}%{$FUNCSIG}"
                 " (overload_id = %d)",
                 is_member, int (which));

        const rw_pid_t pid = rw_process_create (argv_ [0], argv_);
        if (-1 == pid) {
            rw_assert (0, 0, 0,
                       "failed to create process to exercise"
                       " %{?}%{$CLASS}::%{;}%{$FUNCSIG}"
                       " (overload_id = %d)",
                       is_member, int (which));
        }
        else {
            int result = 1;
            rw_waitpid (pid, &result);

            if (0 != result)
                ++fails;

            rw_assert (0 == result, 0, 0,
                       "the test of %{?}%{$CLASS}::%{;}%{$FUNCSIG}"
                       " (overload_id = %d) failed",
                       is_member, int (which));
        }
    }

    free (argv_);

    return fails;
}

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

static int
_rw_setopts_mt ()
{
    const int nopts =
        rw_setopts (
                    "|-overload_id#0 "  // must be non-negative
                    "|-nloops#0 "       // must be non-negative
                    "|-nthreads#0-* "   // must be in [0, MAX_THREADS]
                    "|-stdout~ "        // to prevent "unknown option" output
                    "|-UserAlloc~",     // to prevent "unknown option" output
                    &rw_opt_overload_id,
                    &rw_opt_nloops,
                    int (MAX_THREADS),
                    &rw_opt_nthreads,
                    &rw_opt_dummy,
                    &rw_opt_dummy,
                    0 /* detect missing handlers */);

    if (5 > nopts) {
        rw_fprintf (rw_stderr,
                    "%s:%d: rw_setopts() failed\n",
                    __FILE__, __LINE__);
        abort ();
        return 1;
    }

    return 0;
}

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

int main (int argc, char *argv[])
{
    // set the additional options
    _rw_setopts_mt ();

    rw_runopts (argc, argv);

    if (0 <= rw_opt_overload_id) {

        // overload_id is specified

        const StringIds::OverloadId which =
            _RWSTD_STATIC_CAST (StringIds::OverloadId, rw_opt_overload_id);

        // check that specified overload_id is present in overloads
        size_t i = 0;
        for (; i < test_count; ++i)
            if (which == overloads [i])
                break;

        if (i >= test_count) {
            // using rw_fprintf() because driver is not
            // initialized at this point
            rw_fprintf (rw_stderr,
                        "%s:%d: invalid overload_id: %d\n",
                        __FILE__, __LINE__, rw_opt_overload_id);
            return 1;
        }

        // test the specified overload_id
        static const StringTestCase dummy_cases [1] = {
#undef TEST
#define TEST(str, arg, off, size, off2, size2, val) { \
    __LINE__, off, size, off2, size2, val,            \
    str, sizeof (str) - 1, arg, sizeof (arg) - 1,     \
    0, 0, 0                                           \
}

            TEST ("[EMAIL PROTECTED]", "abcd", 1, 2, 1, 2, 'y')
        };

        const StringTest test = { which, dummy_cases, 1 };

        int res = rw_run_string_test (argc, argv, __FILE__,
                                      "lib.string.mt",
                                      test_mt_func_array,
                                      &test, 1);

        return (0 == res) ? _rw_status : res;
    }

    // test the all methods
    return rw_test (argc, argv, __FILE__,
                    "lib.string",
                    "thread safety", run_test,
                    "",
                    0 /*sentinel*/);
}
Index: 21.strings.cpp
===================================================================
--- 21.strings.cpp      (revision 425643)
+++ 21.strings.cpp      (working copy)
@@ -153,7 +153,7 @@
 
 /**************************************************************************/
 
-void StringState::
+int StringState::
 assert_equal (const StringState &state, int line, int case_line,
               const char *when) const
 {
@@ -169,6 +169,8 @@
                line, data_, size_, capacity_,
                state.data_, state.size_, state.capacity_,
                when);
+
+    return equal;
 }
 
 /**************************************************************************/
@@ -414,9 +416,9 @@
 // FUNCALL: a string describing the call to the basic_string function
 //          with function with function arguments expanded (as specified
 //          by the TestCase argument)
-static void
-_rw_setvars (const StringFunc     &func,
-             const StringTestCase *pcase = 0)
+void
+rw_setvars (const StringFunc     &func,
+            const StringTestCase *pcase = 0)
 {
     char*  buf     = 0;
     size_t bufsize = 0;
@@ -1296,7 +1298,7 @@
 
         // set the {FUNCALL} environment variable to describe
         // the function call specified by this test case
-        _rw_setvars (func, &tcase);
+        rw_setvars (func, &tcase);
 
         if (test_callback) {
             // invoke the test callback function
@@ -1329,7 +1331,7 @@
     // set the {CLASS}, {FUNC}, and {FUNCSIG} environment
     // variable to the name of the basic_string specializaton
     // and the string function being exercised
-    _rw_setvars (func);
+    rw_setvars (func);
 
     // determine whether the function is a member function
     const bool is_member = 0 != (StringIds::bit_member & test.which);
Index: 21.strings.h
===================================================================
--- 21.strings.h        (revision 425643)
+++ 21.strings.h        (working copy)
@@ -408,7 +408,7 @@
 
         //////////////////////////////////////////////////////////////
         // substr (void) const
-        MEMBER_1 (substr, cstr, void),
+        MEMBER_0 (substr, cstr),
         // substr (size_type) const
         MEMBER_1 (substr, cstr, size),
         // substr (size_type, size_type) const
@@ -541,15 +541,15 @@
         NON_MEMBER_2 (op_greater_equal, cstr, cptr),
 
         //////////////////////////////////////////////////////////////
-        // size ()
+        // size () const
         MEMBER_0 (size, cstr),
 
         //////////////////////////////////////////////////////////////
-        // length ()
+        // length () const
         MEMBER_0 (length, cstr),
 
         //////////////////////////////////////////////////////////////
-        // max_size ()
+        // max_size () const
         MEMBER_0 (max_size, cstr),
 
         //////////////////////////////////////////////////////////////
@@ -559,7 +559,7 @@
         MEMBER_1 (resize, str, size),
 
         //////////////////////////////////////////////////////////////
-        // capacity ()
+        // capacity () const
         MEMBER_0 (capacity, cstr),
 
         //////////////////////////////////////////////////////////////
@@ -573,7 +573,7 @@
         MEMBER_0 (clear, str),
 
         //////////////////////////////////////////////////////////////
-        // empty ()
+        // empty () const
         MEMBER_0 (empty, cstr),
 
         //////////////////////////////////////////////////////////////
@@ -818,7 +818,7 @@
     _RWSTD_SIZE_T capacity_;
 
     // invokes rw_assert() to verify that two states are the same
-    void assert_equal (const StringState&, int, int, const char*) const;
+    int assert_equal (const StringState&, int, int, const char*) const;
 };
 
 

Reply via email to