BDE 4.14.0 Production release
|
Provide thread-safe test utilities for multithreaded components.
!X
!X
STREAM
bsl::cout
bsl::cerr
ASSERTV
. Provided for the sake of ease ofThis component provides a set of macros for the standard BDE test driver assert and print facilities (see the macros defined in bsls_bsltestutil and bslim_testutil ) that are suitable for use in multi-threaded test drivers. This component also provides a utility struct
, bslmt::TestUtil
, that defines a namespace for types and functions that are helpful in multi-threaded test drivers.
BSLMT_TESTUTIL_Q(X)
BSLMT_TESTUTIL_P(X)
BSLMT_TESTUTIL_P_(X)
BSLMT_TESTUTIL_L_
BSLMT_TESTUTIL_T_
These macros guarantee:
bsl::cout
) is not corrupted. (Since bsl::cout
itself was not made thread-safe until C++11 this is a concern on some of our supported platforms.)This component also defines several additions to the set of standard test macros:
BSLMT_TESTUTIL_GUARDED_STREAM(STREAM)
BSLMT_TESTUTIL_COUT
BSLMT_TESTUTIL_CERR
These macros guarantee atomicity of the output for the entire statement up to the terminating semi-colon. For example, if one thread executes:
and another thread executes;
The output will be either:
or
but, despite six separate operations on bsl::cout
, the lines will not be interleaved.
As with the "ASSERT" macros above, these macros do not guarantee thread-safe evaluation of any expressions in the output statement. Thus, if one thread is executing:
and some other thread is executing:
the two output statements are evaluated serially, but the evaluation of the f
and g
functions may require their own synchronization (if they are accessing values that may be in the process of unguarded modification by other threads).
The macros:
work like their counterparts in bsls_bsltestutil and bslim_testutil , except that all output by a given assert is guaranteed not to be interleaved with output from other macros in this component.
These macros provide no synchronization until after failure is detected. If the boolean expression being evaluated is not thread-safe, the caller of the macro must provide synchronization around the macro call.
The LOOP*_ASSERT
macros are considered obsolete and are provided to facilitate the multi-threading of test drivers written using macros from bsls_bsltestutil and bslim_testutil . Use BSLMT_TESTUTIL_ASSERTV
instead.
Similarly to the macros defined in bsls_bsltestutil and bslim_testutil , the assert macros defined here assume that the user has defined a global integer variable named testStatus
and has defined a function:
that outputs the conventional error message and update testStatus
when c
is non-zero. If all access to testStatus
is through the "ASSERT" macros, testStatus
can be an ordinary (non-atomic) int
and aSsErT
need not be thread-safe.
This component also provides a (macro-wrapped) guard object:
BSLMT_TESTUTIL_GUARD
which does no output, but rather prevents the interleaving of output from the other macros until the guard goes out of scope. This feature is useful in the common situation in which one wants to prevent interleaving of several separate uses of the macros.
For example, assuming the conventional shorthand for the standard test macros:
and
consider two threads, one of which executes:
while the other thread executes:
In total, there are eight output operations on bsl::cout
. Though the order of outputs within each thread is guaranteed, there is no guarantee that the entire line of output of either thread will be finished before the other begins. One possible result (among other myriad possibilities) is:
Clearly, such output is difficult to interpret. Fortunately, we have a remedy.
The two sets of output operation can be serialized by requiring the creation of a GUARD
object before each set of output operations, and destroying that object afterwards. The existence of a guard object on the stack of one thread blocks the construction of guard objects by other threads until the existing guard object is destroyed.
The behavior is undefined if guard objects are created anywhere but on the stack (i.e., automatic variables).
Our revised (guarded) code can be written this way in one thread:
and this way in the other thread:
Now, possibility of the two threads interleaving this output has been eliminated and the output will appear in either this order:
or this order:
Note that each individual thread can own several guard objects at a given time. (Recall the constructor blocks if guards exist on other threads). Thus, guarded sections can call, without fear of deadlock, functions that may create their own guard objects.
If a guard exists in a thread, more guards can be created in the same thread with no effect. Guards cannot be created in another thread until all of the guards have been destroyed. So the above example could have been:
and in the other thread:
and the result would have been EXACTLY the same.
This section illustrates intended use of this component.
First, we write a function, sumOfSquares
, to test:
Then, we can write a test driver for this component. We start by providing the standard BDE ASSERT
test macro, which is not thread-safe, and is the same as it is for a test driver using bslim_testutil . The macros in bslmt_testutil ensure that any time this function is called, the global mutex has been acquired.
Next, we define the standard output and ASSERT*
macros, as aliases to the macros defined by this component:
Then, we define global verbosity flags to be used for controlling debug traces. The flags will be set by elided code at the beginning of main
to determine the level of output verbosity the client wants:
Next begin the usage test case, defining a typedef
and some enum
s used by this test case:
Then, using our test macros, we write our test functor that can be run concurrently to test the static function:
Next, we use the GUARD
macro to serialize the initialization of threadIdx
and the x
array. We call bsl::srand
and bsl::rand
, which are not thread-safe, so the calls to them must be mutex-guarded. Because all access to mainThreadIdx
is guarded by the GUARD
call, it does not need to be an atomic variable.
Then we close the block, allowing other threads to do output with the BSLMT_TESTUTIL_*
macros or enter sections guarded by GUARD
s. Now, if we want to do output, we have to acquire the critical section again, which we can do by using the COUT
(aliased to BSLMT_TESTUTIL_COUT
) macro:
Next, if any of the ASSERTV
s following this point fail with no GUARD
call in scope, they will lock the mutex before doing output. Note that the ASSERTV
s do not lock the mutex while checking to see if the predicate passed to them is false
.
Then, if we want to do any more output, since the mutex has not been acquired at this point, we have to re-acquire it. We have a choice between using COUT
again, as we did above, or by using GUARD
and bsl::cout
:
Next, in main
, we spawn our threads and let them run:
Then, we join the threads:
Now, we observe output something like this (tabs eliminated, long lines wrapped). Note that each of the five test threads reported a failure:
Finally, at the end of main
examine testStatus
. If it's greater than 0, report that the test failed. Note that since there is a bug in SU::sumOfSquares
with 4 args, we expect the last assert in SumUtil::operator()
to fail 5 times, so the following message will report test status = 5
.
#define BSLMT_TESTUTIL_ASSERT | ( | X | ) | BSLMT_TESTUTIL_LOOP0_ASSERT(X) |
#define BSLMT_TESTUTIL_ASSERTV | ( | ... | ) |
#define BSLMT_TESTUTIL_CERR BSLMT_TESTUTIL_GUARDED_STREAM(bsl::cerr) |
Equivalent to call to bsl::cerr
guarded by a BSLMT_TESTUTIL_GUARD
whose scope ends at the end of the statement.
#define BSLMT_TESTUTIL_COUT BSLMT_TESTUTIL_GUARDED_STREAM(bsl::cout) |
Equivalent to call to bsl::cout
guarded by a BSLMT_TESTUTIL_GUARD
whose scope ends at the end of the statement.
#define BSLMT_TESTUTIL_EXPAND | ( | X | ) | X |
#define BSLMT_TESTUTIL_GUARD BloombergLP::bslmt::TestUtil_Guard BSLMT_TESTUTIL_GUARD_NAME_IMPL |
Acquire the critical section, and release it when the macro call goes out of scope. If the critical section is already held by the current thread, this can still be used with no additional effect.
#define BSLMT_TESTUTIL_GUARD_NAME_IMPL bloomberglp_bslmt_tEsTuTiL_GuArD_ ## __LINE__ |
#define BSLMT_TESTUTIL_GUARDED_STREAM | ( | STREAM | ) |
Enable output to the specified STREAM
that will not be interleaved with output from bslmt_testutil macros done by other threads. STREAM
is expected to be a bsl::ostream
object, and this macro call can be output to with the C++ <<
operator with all output until the terminating ;
occurring as one non-interleaved block.
#define BSLMT_TESTUTIL_L_ __LINE__ |
#define BSLMT_TESTUTIL_LOOP0_ASSERT | ( | X | ) |
#define BSLMT_TESTUTIL_LOOP1_ASSERT | ( | I, | |
X | |||
) |
#define BSLMT_TESTUTIL_LOOP2_ASSERT | ( | I, | |
J, | |||
X | |||
) |
#define BSLMT_TESTUTIL_LOOP3_ASSERT | ( | I, | |
J, | |||
K, | |||
X | |||
) |
#define BSLMT_TESTUTIL_LOOP4_ASSERT | ( | I, | |
J, | |||
K, | |||
L, | |||
X | |||
) |
#define BSLMT_TESTUTIL_LOOP5_ASSERT | ( | I, | |
J, | |||
K, | |||
L, | |||
M, | |||
X | |||
) |
#define BSLMT_TESTUTIL_LOOP6_ASSERT | ( | I, | |
J, | |||
K, | |||
L, | |||
M, | |||
N, | |||
X | |||
) |
#define BSLMT_TESTUTIL_LOOP_ASSERT | ( | X | ) | BSLMT_TESTUTIL_LOOP1_ASSERT(X) |
#define BSLMT_TESTUTIL_LOOPN_ASSERT | ( | N, | |
... | |||
) | BSLMT_TESTUTIL_LOOPN_ASSERT_IMPL(N, __VA_ARGS__) |
#define BSLMT_TESTUTIL_LOOPN_ASSERT_IMPL | ( | N, | |
... | |||
) | BSLMT_TESTUTIL_EXPAND(BSLMT_TESTUTIL_LOOP ## N ## _ASSERT(__VA_ARGS__)) |
#define BSLMT_TESTUTIL_NESTED_OUTPUT_GUARD BSLMT_TESTUTIL_GUARD |
#define BSLMT_TESTUTIL_NUM_ARGS | ( | ... | ) |
#define BSLMT_TESTUTIL_NUM_ARGS_IMPL | ( | X6, | |
X5, | |||
X4, | |||
X3, | |||
X2, | |||
X1, | |||
X0, | |||
N, | |||
... | |||
) | N |
#define BSLMT_TESTUTIL_OUTPUT_GUARD BSLMT_TESTUTIL_GUARD |
#define BSLMT_TESTUTIL_P | ( | X | ) | BSLMT_TESTUTIL_COUT << #X " = " << (X) << bsl::endl |
#define BSLMT_TESTUTIL_P_ | ( | X | ) | BSLMT_TESTUTIL_COUT << #X " = " << (X) << ", " << bsl::flush |
#define BSLMT_TESTUTIL_Q | ( | X | ) | BSLMT_TESTUTIL_COUT << "<| " #X " |>" << bsl::endl |
#define BSLMT_TESTUTIL_T_ BSLMT_TESTUTIL_COUT << "\t" << bsl::flush; |
Print tab (w/o newline). Do not put in a do {} while(false)
, as this macro is intended to be called without a terminating ;
.