> -----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;
};