the symbol table portion of RMr. Run with:
ksh unit_test.ksh symtab_test.c
Date: 1 April 2019
- Author: E. Scott Daniels
+ Author: E. Scott Daniels
*/
+#include <pthread.h>
+
#define NO_DUMMY_RMR 1 // no dummy rmr functions; we don't pull in rmr.h or agnostic.h
#define NO_EMULATION
#define NO_PRIVATE_HEADERS
#include "symtab.c" // module under test
+int terrors = 0; // thread errors
int state = GOOD; // overall pass/fail state 0==fail
int counter; // global counter for for-each tests
-static void fetch( void* st, char* key, int class, int expected ) {
+static int fetch( void* st, char* key, int class, int expected ) {
char* val;
+ int error = 0;
val = rmr_sym_get( st, key, class );
if( val ) {
fprintf( stderr, "[%s] get returns key=%s val=%s\n", !expected ? "FAIL" : "OK", key, val );
if( !expected ) {
state = BAD;
+ error = 1;
}
} else {
fprintf( stderr, "[%s] string key fetch return nil\n", expected ? "FAIL" : "OK" );
if( expected ) {
state = BAD;
+ error = 1;
}
}
+
+ return error;
}
-static void nfetch( void* st, int key, int expected ) {
+static int nfetch( void* st, int key, int expected ) {
char* val;
+ int error = 0;
val = rmr_sym_pull( st, key );
if( val ) {
fprintf( stderr, "[%s] get returns key=%d val=%s\n", !expected ? "FAIL" : "OK", key, val );
if( !expected ) {
state = BAD;
+ error = 1;
}
} else {
fprintf( stderr, "[%s] get return nil for key=%d\n", expected ? "FAIL" : "OK", key );
if( expected ) {
state = BAD;
+ error = 1;
+ }
+ }
+
+ return error;
+}
+
+// ----------------- thread based tests -------------------------------------------------------------------
+#define NUM_KEYS 512 // number of unique keys
+#define NUM_ATTEMPTS 1000000
+
+/*
+ This is started in a thread and will attempt 10,000 reads on the symtable
+ in an attempt to ensure that there are no concurrent read/write issues.
+*/
+static void* reader( void* st ) {
+ char key[1024];
+ int i;
+ int ncount = 0; // number not found
+ int fcount = 0; // number found
+
+ for( i = 0; i < NUM_ATTEMPTS; i++ ) {
+ snprintf( key, sizeof( key ), "key_%d", i % NUM_KEYS );
+ if( rmr_sym_get( st, key, 1 ) == NULL ) {
+ ncount++;
+ } else {
+ fcount++;
+ }
+ }
+
+ fprintf( stderr, "<info> reader finished: n=%d f=%d\n", ncount, fcount ); // there is no right answer
+ return NULL;
+}
+
+/*
+ This is started in a thread and will attempt 10,000 writes on the symtable
+ in an attempt to ensure that there are no concurrent read/write issues. Keys are
+ written as key_n where n is an integer between 0 and 999 inclusive.
+*/
+static void* writer( void* st ) {
+ char key[1024];
+ int i;
+ int ncount = 0; // number first inserts
+ int rcount = 0; // number replacements
+ char* value = NULL;
+ int num_keys = 256;
+
+ fprintf( stderr, "<INFO> writer now turning\n" );
+ for( i = 0; i < NUM_ATTEMPTS; i++ ) {
+ value++;
+ snprintf( key, sizeof( key ), "key_%d", i % NUM_KEYS );
+ rmr_sym_del( st, key, 1 );
+ if( rmr_sym_put( st, key, 1, value ) ) {
+ ncount++;
+ } else {
+ rcount++;
}
}
+
+ if( ncount != NUM_ATTEMPTS ) {
+ fprintf( stderr, "<FAIL> writer finished: n=%d r=%d\n", ncount, rcount ); // there is no right answer
+ terrors++;
+ } else {
+ fprintf( stderr, "<INFO> writer finished: n=%d r=%d\n", ncount, rcount ); // there is no right answer
+ }
+
+ return NULL;
+}
+
+/*
+ Drive a concurrent read/write test to ensure no race issues.
+*/
+static int thread_test( ) {
+ pthread_t tids[10];
+ int n2start = 3;
+ int i;
+ void* st;
+
+ st = rmr_sym_alloc( 128 ); // should force collisions
+
+ fprintf( stderr, "<INFO> starting writer\n" );
+ pthread_create( &tids[0], NULL, writer, st );
+
+ for( i = 1; i <= n2start; i++ ) {
+ fprintf( stderr, "<INFO> starting reader %d\n", i );
+ pthread_create( &tids[i], NULL, reader, st );
+ }
+
+ fprintf( stderr, "<INFO> thread controller is waiting\n" );
+ for( i = 0; i <= n2start; i++ ) {
+ pthread_join( tids[i], NULL ); // status is unimportant, just hold until all are done
+ fprintf( stderr, "<INFO> thread %d has reported complete\n", i );
+ }
+
+
+ rmr_sym_stats( st, 1 );
+ return terrors;
}
+// ---------------------------------------------------------------------------------------------------------
/*
Driven by foreach class -- just incr the counter.
int class = 1;
int s;
void* p;
+ int errors = 0;
st = rmr_sym_alloc( 10 ); // alloc with small value to force adjustment inside
- fail_if_nil( st, "symtab pointer" );
+ errors += fail_if_nil( st, "symtab pointer" );
s = rmr_sym_put( st, foo, class, bar ); // add entry with string key; returns 1 if it was inserted
- fail_if_false( s, "insert foo existed" );
+ errors += fail_if_false( s, "insert foo existed" );
s = rmr_sym_put( st, foo, class+1, bar ); // add to table with a different class
- fail_if_false( s, "insert foo existed" );
+ errors += fail_if_false( s, "insert foo existed" );
s = rmr_sym_put( st, foo, class, bar ); // inserted above, should return not inserted (0)
- fail_if_true( s, "insert foo existed" );
+ errors += fail_if_true( s, "insert foo existed" );
- fetch( st, foo, class, 1 );
- fetch( st, goo, class, 0 ); // fetch non existant
+ errors += fetch( st, foo, class, 1 );
+ errors += fetch( st, goo, class, 0 ); // fetch non existant
rmr_sym_stats( st, 4 ); // early stats at verbose level 4 so chatter is minimised
rmr_sym_dump( st );
- for( i = 2000; i < 3000; i++ ) { // bunch of dummy things to force chains in the table
+ for( i = 2000; i < 3000; i++ ) { // bunch of dummy things to force chains in the table
rmr_sym_map( st, i, foo ); // add entry with unsigned integer key
}
rmr_sym_stats( st, 0 ); // just the small facts to verify the 1000 we stuffed in
rmr_sym_ndel( st, 12001 ); // delete numeric key not there
s = rmr_sym_map( st, 1234, foo ); // add known entries with unsigned integer key
- fail_if_false( s, "numeric add of key 1234 should not have existed" );
+ errors += fail_if_false( s, "numeric add of key 1234 should not have existed" );
s = rmr_sym_map( st, 2345, bar );
fail_if_true( s, "numeric add of key 2345 should have existed" );
counter = 0;
rmr_sym_foreach_class( st, 0, each_counter, NULL );
- fail_if_false( counter, "expected counter after foreach to be non-zero" );
-
- nfetch( st, 1234, 1 );
- nfetch( st, 2345, 1 );
+ errors += fail_if_false( counter, "expected counter after foreach to be non-zero" );
+ errors += nfetch( st, 1234, 1 );
+ errors += nfetch( st, 2345, 1 );
rmr_sym_del( st, foo, 0 );
rmr_sym_free( NULL ); // ensure it doesn't barf when given a nil pointer
rmr_sym_free( st );
- return state;
+ test_summary( errors, "symtab tests" );
+ if( state + errors == 0 ) {
+ fprintf( stderr, "<PASS> all symtab tests were OK\n\n" );
+ } else {
+ fprintf( stderr, "<FAIL> %d errors in symtab code\n\n", errors );
+ }
+
+ errors += thread_test();
+
+ return !!(state + errors);
}