ICU-3156 u_setIncDecFunctions(), API Docs
X-SVN-Rev: 12799
This commit is contained in:
parent
e71305bba2
commit
c934d47697
@ -66,34 +66,32 @@ static int32_t gRecursionCount = 0;
|
||||
|
||||
|
||||
/*
|
||||
* User mutex implementatin functions. If non-null, call back to these rather than
|
||||
* User mutex implementation functions. If non-null, call back to these rather than
|
||||
* directly using the system (Posix or Windows) APIs.
|
||||
* (declarations are in uclean.h)
|
||||
*/
|
||||
static UMtxInit *pMutexInit = NULL;
|
||||
static UMtxDestroy *pMutexDestroy = NULL;
|
||||
static UMtxLock *pMutexLock = NULL;
|
||||
static UMtxUnlock *pMutexUnlock = NULL;
|
||||
static const void *gMutexContext = NULL;
|
||||
static UMtxInit *pMutexInit = NULL;
|
||||
static UMtxFunc *pMutexDestroy = NULL;
|
||||
static UMtxFunc *pMutexLock = NULL;
|
||||
static UMtxFunc *pMutexUnlock = NULL;
|
||||
static const void *gMutexContext = NULL;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* umtx_lock
|
||||
* umtx_lock
|
||||
*/
|
||||
U_CAPI void U_EXPORT2
|
||||
umtx_lock(UMTX *mutex)
|
||||
{
|
||||
if (mutex == NULL)
|
||||
{
|
||||
if (mutex == NULL) {
|
||||
mutex = &gGlobalMutex;
|
||||
}
|
||||
|
||||
if (*mutex == NULL)
|
||||
{
|
||||
if (*mutex == NULL) {
|
||||
/* Attempt to lock an uninitialized mutex. Not Supported.
|
||||
* Note that earlier versions of ICU supported lazy mutex initialization.
|
||||
* That is not thread safe on CPUs that reorder memory operations. */
|
||||
* That was not thread safe on CPUs that reorder memory operations. */
|
||||
U_ASSERT(FALSE);
|
||||
umtx_init(mutex); /* But, in case someone really screwed up, we will
|
||||
* still do the lazy init to try to avoid a crash */
|
||||
@ -265,13 +263,13 @@ umtx_init(UMTX *mutex)
|
||||
*/
|
||||
U_CAPI void U_EXPORT2
|
||||
umtx_destroy(UMTX *mutex) {
|
||||
if (mutex == NULL) /* destroy the global mutex */
|
||||
{
|
||||
if (mutex == NULL) { /* destroy the global mutex */
|
||||
mutex = &gGlobalMutex;
|
||||
}
|
||||
|
||||
if (*mutex == NULL) /* someone already did it. */
|
||||
if (*mutex == NULL) { /* someone already did it. */
|
||||
return;
|
||||
}
|
||||
|
||||
if (pMutexDestroy != NULL) {
|
||||
(*pMutexDestroy)(gMutexContext, mutex);
|
||||
@ -300,7 +298,7 @@ umtx_destroy(UMTX *mutex) {
|
||||
|
||||
|
||||
U_CAPI void U_EXPORT2
|
||||
u_setMutexFunctions(const void *context, UMtxInit *i, UMtxDestroy *d, UMtxLock *l, UMtxUnlock *u,
|
||||
u_setMutexFunctions(const void *context, UMtxInit *i, UMtxFunc *d, UMtxFunc *l, UMtxFunc *u,
|
||||
UErrorCode *status) {
|
||||
if (U_FAILURE(*status)) {
|
||||
return;
|
||||
@ -337,10 +335,103 @@ u_setMutexFunctions(const void *context, UMtxInit *i, UMtxDestroy *d, UMtxLock *
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------
|
||||
*
|
||||
* Atomic Increment and Decrement
|
||||
* umtx_atomic_inc
|
||||
* umtx_atomic_dec
|
||||
*
|
||||
*----------------------------------------------------------------*/
|
||||
|
||||
/* Pointers to user-supplied inc/dec functions. Null if not funcs have been set. */
|
||||
static UMtxAtomicF *pIncF = NULL;
|
||||
static UMtxAtomicF *pDecF = NULL;
|
||||
static void *gIncDecContext;
|
||||
|
||||
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
umtx_atomic_inc(int32_t *p) {
|
||||
int32_t retVal;
|
||||
if (pIncF) {
|
||||
retVal = (*pIncF)(gIncDecContext, p);
|
||||
} else {
|
||||
#if defined (WIN32) && ICU_USE_THREADS == 1
|
||||
retVal = InterlockedIncrement(p);
|
||||
#elif defined (POSIX) && ICU_USE_THREADS == 1
|
||||
pthread_mutex_t *m = (pthread_mutex_t*) gIncDecMutex;
|
||||
pthread_mutex_lock(m);
|
||||
retVal = ++(*p);
|
||||
pthread_mutex_unlock(m);
|
||||
#else
|
||||
/* Unknown Platform, or ICU thread support compiled out. */
|
||||
retVal = ++(*p);
|
||||
#endif
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
umtx_atomic_dec(int32_t *p) {
|
||||
int32_t retVal;
|
||||
if (pDecF) {
|
||||
retVal = (*pDecF)(gIncDecContext, p);
|
||||
} else {
|
||||
#if defined (WIN32) && ICU_USE_THREADS == 1
|
||||
retVal = InterlockedDecrement(p);
|
||||
#elif defined (POSIX) && ICU_USE_THREADS == 1
|
||||
pthread_mutex_t *m = (pthread_mutex_t*) gIncDecMutex;
|
||||
pthread_mutex_lock(m);
|
||||
retVal = --(*p);
|
||||
pthread_mutex_unlock(m);
|
||||
#else
|
||||
/* Unknown Platform, or ICU thread support compiled out. */
|
||||
retVal = --(*p);
|
||||
#endif
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/* TODO: Some POSIXy platforms have atomic inc/dec functions available. Use them. */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
U_CAPI void U_EXPORT2
|
||||
u_setAtomicIncDecFunctions(const void *context, UMtxAtomicF *ip, UMtxAtomicF *dp,
|
||||
UErrorCode *status) {
|
||||
int32_t testInt;
|
||||
if (U_FAILURE(*status)) {
|
||||
return;
|
||||
}
|
||||
/* Can not set a mutex function to a NULL value */
|
||||
if (ip==NULL || dp==NULL) {
|
||||
*status = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
return;
|
||||
}
|
||||
/* If ICU is not in an initial state, disallow this operation. */
|
||||
if (cmemory_inUse()) {
|
||||
*status = U_INVALID_STATE_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
pIncF = ip;
|
||||
pDecF = dp;
|
||||
|
||||
testInt = 0;
|
||||
U_ASSERT(umtx_atomic_inc(&testInt) == 1); /* Sanity Check. Do the functions work at all? */
|
||||
U_ASSERT(testInt == 1);
|
||||
U_ASSERT(umtx_atomic_dec(&testInt) == 0);
|
||||
U_ASSERT(testInt == 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Mutex Cleanup Function
|
||||
*
|
||||
* Destroy the global mutex, and reset the mutex function callback pointers.
|
||||
* Destroy the global mutex(es), and reset the mutex function callback pointers.
|
||||
*/
|
||||
U_CFUNC UBool umtx_cleanup(void) {
|
||||
umtx_destroy(NULL);
|
||||
@ -349,93 +440,11 @@ U_CFUNC UBool umtx_cleanup(void) {
|
||||
pMutexLock = NULL;
|
||||
pMutexUnlock = NULL;
|
||||
gMutexContext = NULL;
|
||||
|
||||
pIncF = NULL;
|
||||
pDecF = NULL;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------
|
||||
*
|
||||
* umtx_atomic_inc
|
||||
* umtx_atomic_dec
|
||||
*
|
||||
*----------------------------------------------------------------*/
|
||||
#if (ICU_USE_THREADS == 1)
|
||||
|
||||
#if defined (WIN32)
|
||||
/*
|
||||
* Win32 - use the Windows API functions for atomic increment and decrement.
|
||||
*/
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
umtx_atomic_inc(int32_t *p)
|
||||
{
|
||||
return InterlockedIncrement(p);
|
||||
}
|
||||
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
umtx_atomic_dec(int32_t *p)
|
||||
{
|
||||
return InterlockedDecrement(p);
|
||||
}
|
||||
|
||||
#elif defined (POSIX)
|
||||
/*
|
||||
* POSIX platforms without specific atomic operations. Use a posix mutex
|
||||
* to protect the increment and decrement.
|
||||
*/
|
||||
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
umtx_atomic_inc(int32_t *p)
|
||||
{
|
||||
int32_t retVal;
|
||||
|
||||
pthread_mutex_t *m = (pthread_mutex_t*) gIncDecMutex;
|
||||
pthread_mutex_lock(m);
|
||||
retVal = ++(*p);
|
||||
pthread_mutex_unlock(m);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
umtx_atomic_dec(int32_t *p)
|
||||
{
|
||||
int32_t retVal;
|
||||
|
||||
pthread_mutex_t *m = (pthread_mutex_t*) gIncDecMutex;
|
||||
pthread_mutex_lock(m);
|
||||
retVal = --(*p);
|
||||
pthread_mutex_unlock(m);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
/* TODO: Some POSIXy platforms have atomic inc/dec functions available. Use them. */
|
||||
#else
|
||||
|
||||
/* No recognized platform. */
|
||||
#error No atomic increment and decrement defined for this platform. \
|
||||
Either use the --disable-threads configure option, or define those functions in this file.
|
||||
|
||||
#endif /* Platform selection for atomic_inc and dec. */
|
||||
|
||||
|
||||
#else /* (ICU_USE_THREADS == 0) */
|
||||
|
||||
/* Threads disabled here */
|
||||
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
umtx_atomic_inc(int32_t *p) {
|
||||
return ++(*p);
|
||||
}
|
||||
|
||||
U_CAPI int32_t U_EXPORT2
|
||||
umtx_atomic_dec(int32_t *p) {
|
||||
return --(*p);
|
||||
}
|
||||
|
||||
#endif /* (ICU_USE_THREADS == 1) */
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -92,34 +92,142 @@ u_cleanup(void);
|
||||
|
||||
|
||||
/**
|
||||
* An opaque type that represents an ICU mutex.
|
||||
* An opaque pointer type that represents an ICU mutex.
|
||||
* For user-implemented mutexes, the value will typically point to a
|
||||
* struct or object that implements the mutex.
|
||||
* @draft ICU 2.8
|
||||
* @system
|
||||
*/
|
||||
typedef void *UMTX;
|
||||
|
||||
typedef void U_CALLCONV UMtxInit (const void *context, UMTX *mutex, UErrorCode*pError);
|
||||
typedef void U_CALLCONV UMtxDestroy(const void *context, UMTX *mutex);
|
||||
typedef void U_CALLCONV UMtxLock (const void *context, UMTX *mutex);
|
||||
typedef void U_CALLCONV UMtxUnlock (const void *context, UMTX *mutex);
|
||||
/**
|
||||
* Function Pointer type for a user supplied mutex initialization function.
|
||||
* The user-supplied function will be called by ICU whenever ICU needs to create a
|
||||
* new mutex. The function implementation should create a mutex, and store a pointer
|
||||
* to something that uniquely identifies the mutex into the UMTX that is supplied
|
||||
* as a paramter.
|
||||
* @param context user supplied value, obtained from from u_setMutexFunctions().
|
||||
* @param mutex Receives a pointer that identifies the new mutex.
|
||||
* The mutex init function must set the UMTX to a non-null value.
|
||||
* Subsequent calls by ICU to lock, unlock, or destroy a mutex will
|
||||
* identify the mutex by the UMTX value.
|
||||
* @param status Error status. Report errors back to ICU by setting this variable
|
||||
* with an error code.
|
||||
*/
|
||||
typedef void U_CALLCONV UMtxInit (const void *context, UMTX *mutex, UErrorCode* pError);
|
||||
|
||||
|
||||
/**
|
||||
* Function Pointer type for a user supplied mutex functions.
|
||||
* One of the user-supplied functions with this signature will be called by ICU
|
||||
* whenever ICU needs to lock, unlock, or destroy a mutex.
|
||||
* @param context user supplied value, obtained from from u_setMutexFunctions().
|
||||
* @param mutex specify the mutex on which to operate.
|
||||
* @draft ICU 2.8
|
||||
* @system
|
||||
*/
|
||||
typedef void U_CALLCONV UMtxFunc (const void *context, UMTX *mutex);
|
||||
|
||||
|
||||
/**
|
||||
* Set the functions that ICU will use for mutex operations
|
||||
* Use of this function is optional; by default (without this function), ICU will
|
||||
* directly access system functions for mutex operations
|
||||
* This function can only be used when ICU is in an initial, unused state, before
|
||||
* u_init() has been called.
|
||||
* This function may be used even when ICU has been built without multi-threaded
|
||||
* support (see ICU_USE_THREADS pre-processor variable)
|
||||
* @param context This pointer value will be saved, and then (later) passed as
|
||||
* a parameter to the increment and decrement functions each time they
|
||||
* are called. This function can only be called
|
||||
* @param inc Pointer to a function to do an atomic increment operation. Must be non-null.
|
||||
* @param dec Pointer to a function to do an atomic decrement operation. Must be non-null.
|
||||
* @param status Receives error values.
|
||||
* @draft ICU 2.8
|
||||
* @system
|
||||
*/
|
||||
U_CAPI void U_EXPORT2
|
||||
u_setMutexFunctions(const void *context, UMtxInit *i, UMtxDestroy *d, UMtxLock *l, UMtxUnlock *u,
|
||||
u_setMutexFunctions(const void *context, UMtxInit *i, UMtxFunc *d, UMtxFunc *l, UMtxFunc *u,
|
||||
UErrorCode *status);
|
||||
|
||||
|
||||
typedef void U_CALLCONV UMtxAtomicInc (const void *context, UMTX mutex);
|
||||
typedef void U_CALLCONV UMtxAtomicDec (const void *context, UMTX mutex);
|
||||
/**
|
||||
* Pointer type for a user supplied atomic increment or decrement function.
|
||||
* @param context user supplied value, obtained from from u_setAtomicIncDecFunctions().
|
||||
* @param p Pointer to a 32 bit int to be incremented or decremented
|
||||
* @return The value of the variable after the inc or dec operation.
|
||||
* @draft ICU 2.8
|
||||
* @system
|
||||
*/
|
||||
typedef int32_t U_CALLCONV UMtxAtomicF (const void *context, int32_t *p);
|
||||
|
||||
/**
|
||||
* Set the functions that ICU will use for atomic increment and decrement of int32_t values.
|
||||
* Use of this function is optional; by default (without this function), ICU will
|
||||
* use its own internal implementation of atomic increment/decrement.
|
||||
* This function can only be used when ICU is in an initial, unused state, before
|
||||
* u_init() has been called.
|
||||
* @param context This pointer value will be saved, and then (later) passed as
|
||||
* a parameter to the increment and decrement functions each time they
|
||||
* are called. This function can only be called
|
||||
* @param inc Pointer to a function to do an atomic increment operation. Must be non-null.
|
||||
* @param dec Pointer to a function to do an atomic decrement operation. Must be non-null.
|
||||
* @param status Receives error values.
|
||||
* @draft ICU 2.8
|
||||
* @system
|
||||
*/
|
||||
U_CAPI void U_EXPORT2
|
||||
u_setAtomicIncDecFunctions(const void *context, UMtxAtomicInc *inc, UMtxAtomicDec *dec,
|
||||
u_setAtomicIncDecFunctions(const void *context, UMtxAtomicF *inc, UMtxAtomicF *dec,
|
||||
UErrorCode *status);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Pointer type for a user supplied memory allocation function.
|
||||
* @param context user supplied value, obtained from from u_setMemoryFunctions().
|
||||
* @param size The number of bytes to be allocated
|
||||
* @return Pointer to the newly allocated memory, or NULL if the allocation failed.
|
||||
* @draft ICU 2.8
|
||||
* @system
|
||||
*/
|
||||
typedef void *U_CALLCONV UMemAlloc (const void *context, size_t size);
|
||||
/**
|
||||
* Pointer type for a user supplied memory re-allocation function.
|
||||
* @param context user supplied value, obtained from from u_setMemoryFunctions().
|
||||
* @param size The number of bytes to be allocated
|
||||
* @return Pointer to the newly allocated memory, or NULL if the allocation failed.
|
||||
* @draft ICU 2.8
|
||||
* @system
|
||||
*/
|
||||
typedef void *U_CALLCONV UMemRealloc(const void *context, void *mem, size_t size);
|
||||
/**
|
||||
* Pointer type for a user supplied memory free function. Behavior should be
|
||||
* similar the standard C library free().
|
||||
* @param context user supplied value, obtained from from u_setMemoryFunctions().
|
||||
* @param mem Pointer to the memory block to be resized
|
||||
* @param size The new size for the block
|
||||
* @return Pointer to the resized memory block, or NULL if the resizing failed.
|
||||
* @draft ICU 2.8
|
||||
* @system
|
||||
*/
|
||||
typedef void U_CALLCONV UMemFree (const void *context, void *mem);
|
||||
|
||||
/**
|
||||
* Set the functions that ICU will use for memory allocation.
|
||||
* Use of this function is optional; by default (without this function), ICU will
|
||||
* use the standard C library malloc() and free() functions.
|
||||
* This function can only be used when ICU is in an initial, unused state, before
|
||||
* u_init() has been called.
|
||||
* @param context This pointer value will be saved, and then (later) passed as
|
||||
* a parameter to the memory functions each time they
|
||||
* are called.
|
||||
* @param a Pointer to a user-supplied malloc function.
|
||||
* @param r Pointer to a user-supplied realloc function.
|
||||
* @param f Pointer to a user-supplied free function.
|
||||
* @param status Receives error values.
|
||||
* @draft ICU 2.8
|
||||
* @system
|
||||
*/
|
||||
U_CAPI void U_EXPORT2
|
||||
u_setMemoryFunctions(const void *context, UMemAlloc *a, UMemRealloc *r, UMemFree *f,
|
||||
UErrorCode *status);
|
||||
|
@ -13,12 +13,13 @@
|
||||
#include "unicode/uchar.h"
|
||||
#include "unicode/ures.h"
|
||||
#include "cintltst.h"
|
||||
|
||||
#include "umutex.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
static void TestHeapFunctions(void);
|
||||
static void TestMutexFunctions(void);
|
||||
static void TestIncDecFunctions(void);
|
||||
|
||||
void addHeapMutexTest(TestNode **root);
|
||||
|
||||
@ -26,8 +27,9 @@ void addHeapMutexTest(TestNode **root);
|
||||
void
|
||||
addHeapMutexTest(TestNode** root)
|
||||
{
|
||||
addTest(root, &TestHeapFunctions, "tsutil/TestHeapFunctions" );
|
||||
addTest(root, &TestMutexFunctions, "tsutil/TestMutexFunctions");
|
||||
addTest(root, &TestHeapFunctions, "tsutil/TestHeapFunctions" );
|
||||
addTest(root, &TestMutexFunctions, "tsutil/TestMutexFunctions" );
|
||||
addTest(root, &TestIncDecFunctions, "tsutil/TestIncDecFunctions");
|
||||
}
|
||||
|
||||
|
||||
@ -284,3 +286,96 @@ static void TestMutexFunctions() {
|
||||
ures_close(rb);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Test Atomic Increment & Decrement Functions
|
||||
*/
|
||||
|
||||
int gIncCount = 0;
|
||||
int gDecCount = 0;
|
||||
const void *gIncDecContext;
|
||||
|
||||
|
||||
static int32_t myIncFunc(const void *context, int32_t *p) {
|
||||
int32_t retVal;
|
||||
TEST_ASSERT(context == gIncDecContext);
|
||||
gIncCount++;
|
||||
retVal = ++(*p);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
static int32_t myDecFunc(const void *context, int32_t *p) {
|
||||
int32_t retVal;
|
||||
TEST_ASSERT(context == gIncDecContext);
|
||||
gDecCount++;
|
||||
retVal = --(*p);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static void TestIncDecFunctions() {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
int32_t t;
|
||||
|
||||
|
||||
/* Can not set mutex functions if ICU is already initialized */
|
||||
u_setAtomicIncDecFunctions(&gIncDecContext, myIncFunc, myDecFunc, &status);
|
||||
TEST_STATUS(status, U_INVALID_STATE_ERROR);
|
||||
|
||||
/* Un-initialize ICU */
|
||||
u_cleanup();
|
||||
|
||||
/* Can not set functions with NULL values */
|
||||
status = U_ZERO_ERROR;
|
||||
u_setAtomicIncDecFunctions(&gIncDecContext, NULL, myDecFunc, &status);
|
||||
TEST_STATUS(status, U_ILLEGAL_ARGUMENT_ERROR);
|
||||
status = U_ZERO_ERROR;
|
||||
u_setAtomicIncDecFunctions(&gIncDecContext, myIncFunc, NULL, &status);
|
||||
TEST_STATUS(status, U_ILLEGAL_ARGUMENT_ERROR);
|
||||
|
||||
/* u_setIncDecFunctions() should work with null or non-null context pointer */
|
||||
status = U_ZERO_ERROR;
|
||||
u_setAtomicIncDecFunctions(NULL, myIncFunc, myDecFunc, &status);
|
||||
TEST_STATUS(status, U_ZERO_ERROR);
|
||||
u_setAtomicIncDecFunctions(&gIncDecContext, myIncFunc, myDecFunc, &status);
|
||||
TEST_STATUS(status, U_ZERO_ERROR);
|
||||
|
||||
|
||||
/* After reinitializing ICU, we should not be able to set the inc/dec funcs again. */
|
||||
status = U_ZERO_ERROR;
|
||||
u_init(&status);
|
||||
TEST_STATUS(status, U_ZERO_ERROR);
|
||||
u_setAtomicIncDecFunctions(&gIncDecContext, myIncFunc, myDecFunc, &status);
|
||||
TEST_STATUS(status, U_INVALID_STATE_ERROR);
|
||||
|
||||
/* Doing ICU operations should cause our functions to be called */
|
||||
gIncCount = 0;
|
||||
gDecCount = 0;
|
||||
umtx_atomic_inc(&t);
|
||||
umtx_atomic_dec(&t);
|
||||
TEST_ASSERT(gIncCount > 0);
|
||||
TEST_ASSERT(gDecCount > 0);
|
||||
|
||||
|
||||
/* Cleanup should cancel use of our inc/dec functions. */
|
||||
/* Additional ICU operations should not use them */
|
||||
u_cleanup();
|
||||
gIncCount = 0;
|
||||
gDecCount = 0;
|
||||
status = U_ZERO_ERROR;
|
||||
u_init(&status);
|
||||
TEST_ASSERT(gIncCount == 0);
|
||||
TEST_ASSERT(gDecCount == 0);
|
||||
|
||||
status = U_ZERO_ERROR;
|
||||
umtx_atomic_inc(&t);
|
||||
umtx_atomic_dec(&t);
|
||||
TEST_STATUS(status, U_ZERO_ERROR);
|
||||
TEST_ASSERT(gIncCount == 0);
|
||||
TEST_ASSERT(gDecCount == 0);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user