First, we write a function, sumOfSquares
, to test: namespace xyzde {
struct SumUtil {
static double sumOfSquares(double a,
double b = 0,
double c = 0,
double d = 0);
};
inline
double SumUtil::sumOfSquares(double a,
double b,
double c,
double d)
{
double ret = a * a;
ret += b * b;
ret += c * c;
ret += d * d * d;
return ret;
}
}
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.
int testStatus = 0;
void aSsErT(int c, const char *s, int i)
{
if (c) {
bsl::cout << "Error " << __FILE__ << "(" << i << "): " << s
<< " (failed)" << bsl::endl;
if (testStatus >= 0 && testStatus <= 100) ++testStatus;
}
}
Next, we define the standard output and ASSERT*
macros, as aliases to the macros defined by this component:
#define ASSERT BSLMT_TESTUTIL_ASSERT
#define ASSERTV BSLMT_TESTUTIL_ASSERTV
#define LOOP_ASSERT BSLMT_TESTUTIL_LOOP_ASSERT
#define LOOP2_ASSERT BSLMT_TESTUTIL_LOOP2_ASSERT
#define LOOP3_ASSERT BSLMT_TESTUTIL_LOOP3_ASSERT
#define LOOP4_ASSERT BSLMT_TESTUTIL_LOOP4_ASSERT
#define LOOP5_ASSERT BSLMT_TESTUTIL_LOOP5_ASSERT
#define LOOP6_ASSERT BSLMT_TESTUTIL_LOOP6_ASSERT
#define GUARD BSLMT_TESTUTIL_GUARD
#define Q BSLMT_TESTUTIL_Q
#define P BSLMT_TESTUTIL_P
#define P_ BSLMT_TESTUTIL_P_
#define T_ BSLMT_TESTUTIL_T_
#define L_ BSLMT_TESTUTIL_L_
#define GUARDED_STREAM(STREAM) BSLMT_TESTUTIL_GUARDED_STREAM(STREAM)
#define COUT BSLMT_TESTUTIL_COUT
#define CERR BSLMT_TESTUTIL_CERR
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:
bool verbose;
bool veryVerbose;
bool veryVeryVerbose;
bool veryVeryVeryVerbose;
Next begin the usage test case, defining a typedef
and some enum
s used by this test case:
typedef xyzde::SumUtil SU;
enum { k_NUM_THREADS = 5,
k_HI_LIMIT_X = 100,
k_LO_LIMIT_X = -100 };
Then, using our test macros, we write our test functor that can be run concurrently to test the static function: struct SumUtilTest {
void operator()()
{
int threadIdx;
double x[4];
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. {
GUARD;
static int mainThreadIdx = 0;
threadIdx = mainThreadIdx++;
unsigned randSeed = (1234567891 + threadIdx) * 3333333333U;
bsl::srand(randSeed);
for (int ii = 0; ii < 4; ++ii) {
const double characteristic = bsl::rand() % k_HI_LIMIT_X;
const double mantissa =
static_cast<double>(bsl::rand() % 1024) / 1024;
const int sign = (bsl::rand() & 1) ? +1 : -1;
ASSERTV(threadIdx,ii, characteristic, 0 <= characteristic);
ASSERTV(threadIdx,ii, characteristic,
characteristic < k_HI_LIMIT_X);
ASSERTV(threadIdx,ii, mantissa, 0 <= mantissa);
ASSERTV(threadIdx,ii, mantissa, mantissa < 1);
x[ii] = sign * (characteristic + mantissa / 1000);
}
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: }
if (veryVerbose) COUT << "threadIdx: " << threadIdx <<
", x[] = { " << x[0] << ", " << x[1] << ", " << x[2] <<
", " << x[3] <<" }\n";
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
. for (int ii = 0; ii < 4; ++ii) {
ASSERTV(threadIdx, ii, x[ii], x[ii] < k_HI_LIMIT_X);
ASSERTV(threadIdx, ii, x[ii], k_LO_LIMIT_X < x[ii]);
}
double exp = x[0] * x[0];
ASSERTV(x[0], exp, SU::sumOfSquares(x[0]),
exp == SU::sumOfSquares(x[0]));
exp += x[1] * x[1];
ASSERTV(x[0], x[1], exp, SU::sumOfSquares(x[0], x[1]),
exp == SU::sumOfSquares(x[0], x[1]));
exp += x[2] * x[2];
ASSERTV(x[0], x[1], x[2], exp, SU::sumOfSquares(x[0], x[1], x[2]),
exp == SU::sumOfSquares(x[0], x[1], x[2]));
exp += x[3] * x[3];
ASSERTV(x[0], x[1], x[2], x[3], exp,
SU::sumOfSquares(x[0], x[1], x[2], x[3]),
exp == SU::sumOfSquares(x[0], x[1], x[2], x[3]));
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
: if (veryVerbose) {
GUARD;
bsl::cout << "Thread number " << threadIdx << " finishing.\n";
}
}
};
Next, in main
, we spawn our threads and let them run: int main()
{
using namespace BloombergLP;
bslmt::ThreadGroup tg;
tg.addThreads(SumUtilTest(), k_NUM_THREADS);
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: x[0]: 24.0005 x[1]: 80.0001 x[2]: 14.0009 x[3]: 3.00029 exp: 7181.07
SU::sumOfSquares(x[0], x[1], x[2], x[3]): 7199.08
Error ../../bde/groups/bsl/bslmt/bslmt_testutil.t.cpp(380):
exp == SU::sumOfSquares(x[0], x[1], x[2], x[3]) (failed)
x[0]: -81.0006 x[1]: -82.0009 x[2]: 36.0009 x[3]: -59.0002
exp: 18062.3 SU::sumOfSquares(x[0], x[1], x[2], x[3]): -190799
Error ../../bde/groups/bsl/bslmt/bslmt_testutil.t.cpp(380):
exp == SU::sumOfSquares(x[0], x[1], x[2], x[3]) (failed)
x[0]: 46.0001 x[1]: -62.0004 x[2]: 75.0006 x[3]: -66.0008 exp: 15941.3
SU::sumOfSquares(x[0], x[1], x[2], x[3]): -275921
Error ../../bde/groups/bsl/bslmt/bslmt_testutil.t.cpp(380):
exp == SU::sumOfSquares(x[0], x[1], x[2], x[3]) (failed)
x[0]: -18.0003 x[1]: -84.0006 x[2]: 79.0004 x[3]: 76.0007 exp: 19397.3
SU::sumOfSquares(x[0], x[1], x[2], x[3]): 452609
Error ../../bde/groups/bsl/bslmt/bslmt_testutil.t.cpp(380):
exp == SU::sumOfSquares(x[0], x[1], x[2], x[3]) (failed)
x[0]: -55.0006 x[1]: 35.0004 x[2]: 54.0009 x[3]: -45.0002 exp: 9191.21
SU::sumOfSquares(x[0], x[1], x[2], x[3]): -83960.1
Error ../../bde/groups/bsl/bslmt/bslmt_testutil.t.cpp(380):
exp == SU::sumOfSquares(x[0], x[1], x[2], x[3]) (failed)
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
. if (testStatus > 0) {
bsl::cerr << "Error, non-zero test status = " << testStatus << "."
<< bsl::endl;
}
return testStatus;
}