Beef up unit tests for SI95 code
[ric-plt/lib/rmr.git] / test / symtab_test.c
index e2efa2b..9beae8d 100644 (file)
@@ -1,13 +1,13 @@
 /*
 ==================================================================================
-        Copyright (c) 2019 Nokia 
-        Copyright (c) 2018-2019 AT&T Intellectual Property.
+           Copyright (c) 2019-2021 Nokia
+           Copyright (c) 2018-2021 AT&T Intellectual Property.
 
    Licensed 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
+          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,
 /*
        Mnemonic:       symtab_test.c
        Abstract:       This is the unit test module that will drive tests against
-                               the symbol table portion of RMr.  Run with: 
+                               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
 */
 
-#define NO_DUMMY_RMR 1                 // no dummy rmr functions; we don't pull in rmr.h or agnostic.h
+#include <pthread.h>
 
-#include "../src/common/include/rmr_symtab.h"
-#include "../src/common/src/symtab.c"
+#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 <rmr.h>
+#include <rmr_agnostic.h>
 #include "test_support.c"
+#include "rmr_symtab.h"
+
+#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.
@@ -83,59 +194,69 @@ static void each_counter( void* a, void* b, const char* c, void* d, void* e ) {
 }
 
 int main( ) {
-    void*   st;
-    char*   foo = "foo";
-    char*   bar = "bar";
+       void*   st;
+       char*   foo = "foo";
+       char*   bar = "bar";
        char*   goo = "goo";                            // name not in symtab
        int             i;
        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" );
+       st = rmr_sym_alloc( 10 );                                               // alloc with small value to force adjustment inside
+       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" );
+       s = rmr_sym_put( st, foo, class, bar );                 // add entry with string key; returns 1 if it was inserted
+       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" );
+       s = rmr_sym_put( st, foo, class+1, bar );               // add to table with a different class
+       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" );
+       s = rmr_sym_put( st, foo, class, bar );                 // inserted above, should return not inserted (0)
+       errors += fail_if_true( s, "insert foo existed" );
 
-       fetch( st, foo, class, 1 );
-       fetch( st, goo, class, 0 );                                     // fetch non existant
-    rmr_sym_stats( st, 4 );                                                    // early stats at verbose level 4 so chatter is minimised
+       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_stats( st, 0 );                                                 // just the small facts to verify the 1000 we stuffed in
        rmr_sym_ndel( st, 2001 );                                               // force a numeric key delete
        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_del( st, foo, 0 );
 
-    rmr_sym_stats( st, 0 );
+       rmr_sym_stats( st, 0 );
 
        rmr_sym_free( NULL );                   // ensure it doesn't barf when given a nil pointer
        rmr_sym_free( st );
 
-    return state;
+       errors += thread_test();                // test as best we can for race issues
+
+       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 );
+       }
+
+
+       return !!(state + errors);
 }