test(unit): Extend unit tests

Change-Id: I28148ce6ef070ad85cb80fc21f090ff47e856d79
Signed-off-by: E. Scott Daniels <daniels@research.att.com>
diff --git a/test/Makefile b/test/Makefile
index 20d461e..f23f838 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -3,18 +3,28 @@
 CC = gcc
 coverage_opts = -ftest-coverage -fprofile-arcs
 
-libs = ../build/librmr_nng.a -L ../build/lib -lnng -lpthread -lm
+#libs = ../build/librmr_nng.a -L ../build/lib -lnng -lpthread -lm
+libs =  -L ../build/lib -lnng -lpthread -lm
+
+#sa_tests = sa_tools_test.o
 
 %.o:: %.c
 	$(CC) -g $< -c 
 
 %:: %.c
-	$(CC) $(coverage_opts) -fPIC -g $< -o $@  $(libs)
+	$(CC) -I ../src/common/src/ -I ../src/common/include -I ../src/nng/include $(coverage_opts) -fPIC -g $< -o $@  $(libs)
 
 # catch all 
 all:
 	echo "run unit_test.ksh to make and run things here"
 
+
+#sa_tools_test.o: sa_tools_test.c
+#	$(CC) -I ../src/common/src/ -I ../src/common/include -I $(coverage_opts) ../src/nng/include  -fPIC -g $< -c 
+
+#.PHONY: sa_tests
+#sa_tests:	$(sa_tests)
+
 # remove intermediates
 clean:
 	rm -f *.gcov *.gcda *.dcov *.gcno
diff --git a/test/README b/test/README
index 85f96ed..5adc3f8 100644
--- a/test/README
+++ b/test/README
@@ -1,21 +1,25 @@
 
 Unit test
 
-The means to unit testing the RMr library is contained in
-this directory.  It is somewhat difficult to accurately generate
-coverage information for parts of the library because the library
-is a fair amount of static functions (to keep them from being
-visible to the user programme).  
+This directory contains the unit test support for the RMr
+library.  The basic test is run with the follwing command:
 
-To run the tests:
-	ksh unit_test.sh [specific-test]
+	ksh unit_test.ksh
 
-If a specific test (e.g. ring_test.c) is not given on the command line,
-all *_test.c files are sussed out and an attempt to build and run them
-is made by the script. 
+To run a specific test (e.g. ring_test.c) run:
+	ksh unit_test.ksh ring_test.c
 
-Output is an interpretation of the resulting gcov output (in more useful
-and/or easier to read format).  For example:
+The script runs the unit test(s) given, and if they pass then
+runs an analysis on the .gcov files generated to generate
+coverage information.  By default, pass/fail of the test is
+based only on the success or failure of the unit tests which
+are testing functionality.  The unit test script can report
+an overall failure if coverage is below the indicated threshold
+when given the strict option (-s).
+
+The analysis of .gcov files generates output shown below which
+is thought to be more straight forward than the typical stuff
+gcov produces:
 
 unit_test.ksh ring_test.c
 ring_test.c --------------------------------------
@@ -26,18 +30,29 @@
 [PASS]  91% ../src/common/src/ring_static.c
 
 
-The output shows, for each function, the coverage (column 2) and an 
+The output shows, for each function, the coverage (column 2) and an
 interpretation (ok or low) wthin an overall pass or fail.
 
 
-File Names
-The unit test script will find all files named *_test.c and assume that
-they can be compiled and executed using the local Makefile. Files
-which are needed by these programmes (e.g. common functions) are expected
-to reside in this directory as test_*.c and test_*.h files and should 
-be directly included by the test programmes (not built and linked). This 
-allows the unit test script to isngore the functions, and files, when 
-generating coverage reports. 
+Because of the static nature of the RMr library, tests with the
+intent of providing coverage information, as opposed just to providing
+functional verification, are a bit trickier.  To that end, the test
+files in this directory are organised with three file name formats:
+
+	test_*.c  tools for testing, not tests
+
+	*_test.c  main test programmes which can be compiled in
+			  a stand-alone manner (e.g. gcc foo_test.c)
+
+	*_static_test.c  Test functions which are real tests and are
+			  included by one or more stand-alone driver.
+
+The unit_test script will search only for *_test.c and will ignore
+*_static_test.c files when building it's list for testing.
+
+
+Use the command 'unit_test.ksh -?' to see the usage information
+and complete set of options available.
 
 
 Discounting
@@ -51,13 +66,13 @@
 shows the discounted lines with a string of equal signs (====) rather
 than the gcov hash string (###).
 
-The discount check is applied only if an entire module has a lower 
-than accepted coverage rate, and can be forced for all modules with 
+The discount check is applied only if an entire module has a lower
+than accepted coverage rate, and can be forced for all modules with
 the -f option.
 
 To illustrate, the following code checks the return from the system
 library strdup() call which is very unlikely to fail under test without
-going to extremes and substituting for the system lib.  Thus, the 
+going to extremes and substituting for the system lib.  Thus, the
 block which checks for a nil pointer has been discounted:
 
      -:  354:
@@ -70,13 +85,13 @@
 
 Target Coverage
 By default, a target coverage of 80% is used. For some modules this may
-be impossible to achieve, so to prevent always failing these modules 
+be impossible to achieve, so to prevent always failing these modules
 may be listed in the .targets file with their expected minimum coverage.
 Module names need to be qualified (e.g. ../src/common/src/foo.c.
 
 
 -----------------------------------------------------------------------
 A note about ksh (A.K.A Korn shell, or kshell)
-Ksh is preferred for more complex scripts such as the unit test 
+Ksh is preferred for more complex scripts such as the unit test
 script as it does not have some of the limitations that bash
-(and other knock-offs) have. 
+(and other knock-offs) have.
diff --git a/test/hdr_static_test.c b/test/hdr_static_test.c
new file mode 100644
index 0000000..9d7a38c
--- /dev/null
+++ b/test/hdr_static_test.c
@@ -0,0 +1,112 @@
+// : vi ts=4 sw=4 noet :
+/*
+==================================================================================
+        Copyright (c) 2019 Nokia 
+        Copyright (c) 2018-2019 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
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+	Mmemonic:	hdr_static_test.c
+	Abstract:	This tests specific properties of the message header
+
+	Author:		E. Scott Daniels
+	Date:		12 April 2019
+*/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <netdb.h>
+
+#include <nng/nng.h>
+#include <nng/protocol/pubsub0/pub.h>
+#include <nng/protocol/pubsub0/sub.h>
+#include <nng/protocol/pipeline0/push.h>
+#include <nng/protocol/pipeline0/pull.h>
+
+#include "../src/common/include/rmr.h"
+#include "../src/common/include/rmr_agnostic.h"
+#include "../src/nng/include/rmr_nng_private.h"
+
+#define EMULATE_NNG
+#include "test_nng_em.c"
+#include "../src/nng/src/sr_nng_static.c"
+
+#include "test_support.c"
+
+/*
+	Dummy for testing here
+*/
+extern void rmr_free_msg( rmr_mbuf_t* mbuf ) {
+}
+
+static int hdr_test( ) {
+	int errors = 0;
+	uta_ctx_t*	ctx;
+	rmr_mbuf_t*	msg;
+	uta_mhdr_t*	hdr;
+	int hlen;
+	int len;
+	int	payload_len = 2049;
+	int trace_len = 37;
+
+	ctx = (uta_ctx_t *) malloc( sizeof( *ctx ) );
+	ctx->trace_data_len = 0;
+	ctx->my_name = strdup( "my-dummy-host-name-and-port:xxxx" );
+
+	msg = alloc_zcmsg( ctx, NULL, payload_len, 0 );				// header len here should just be len of our struct
+	hdr = (uta_mhdr_t *) msg->header;
+	hlen = RMR_HDR_LEN( hdr );
+
+	fprintf( stderr, "<INFO> struct len= %d  msg len= %d %d\n", (int) sizeof( uta_mhdr_t ), hlen, htonl( hlen ) );
+	errors += fail_not_equal( hlen, (int) sizeof( uta_mhdr_t ), "header len (a) not size of struct when no trace data is present" );
+
+	len = (int) sizeof( uta_mhdr_t ) + payload_len;					// expected size of transport buffer allocated
+	errors += fail_not_equal( len, msg->alloc_len, "alloc len (a)  not expected size" );
+
+
+	ctx->trace_data_len = trace_len;		// alloc messages with tracing buffer in place
+	msg = alloc_zcmsg( ctx, NULL, payload_len, 0 );				// header len here should just be len of our struct
+	hdr = (uta_mhdr_t *) msg->header;
+	hlen = RMR_HDR_LEN( hdr );
+	fprintf( stderr, "<INFO> with trace data: struct+trace len= %d  msg len= %d %d\n", (int) sizeof( uta_mhdr_t )+trace_len, hlen, htonl( hlen ) );
+	errors += fail_not_equal( hlen, (int) sizeof( uta_mhdr_t ) + trace_len, "header len (a) was not header + trace data size (b)" );
+
+	len = RMR_TR_LEN( hdr );
+	errors += fail_not_equal( len, trace_len, "trace len in header (a) not expected value (b)" );
+
+	len = RMR_D1_LEN( hdr );
+	errors += fail_not_equal( len, 0, "d1 len in header (a) not expected value (b)" );
+
+	len = RMR_D2_LEN( hdr );
+	errors += fail_not_equal( len, 0, "d2 len in header (a) not expected value (b)" );
+
+
+	// -------------------------------------------------------------------------------------------
+
+	if( ! errors ) {
+		fprintf( stderr, "<INFO> all msg header tests pass\n" );
+	}
+	return !! errors;
+}
+
+int main() {
+	return hdr_test();
+}
diff --git a/test/mbuf_api_static_test.c b/test/mbuf_api_static_test.c
new file mode 100644
index 0000000..4c0cd5e
--- /dev/null
+++ b/test/mbuf_api_static_test.c
@@ -0,0 +1,188 @@
+// : vi ts=4 sw=4 noet :
+/*
+==================================================================================
+        Copyright (c) 2019 Nokia 
+        Copyright (c) 2018-2019 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
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+	Mmemonic:	mbuf_api_static_test.c
+	Abstract:	Test the message buffer  funcitons. These are meant to be included at compile
+				time by the test driver.  
+
+	Author:		E. Scott Daniels
+	Date:		3 April 2019
+*/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "../src/common/include/rmr.h"
+#include "../src/common/include/rmr_agnostic.h"
+
+
+int mbuf_api_test( ) {
+	unsigned char* c;
+	int i;
+	int errors = 0;
+	rmr_mbuf_t*	mbuf;
+	unsigned char src_buf[256];
+
+	mbuf = (rmr_mbuf_t *) malloc( sizeof( *mbuf ) );
+	if( mbuf == NULL ) {
+		fprintf( stderr, "[FAIL] tester cannot allocate memory: mbuf\n" );
+		exit( 1 );
+	}
+
+	mbuf->payload = (void *) malloc( sizeof( char ) * 1024 );		// add a dummy payload
+	mbuf->tp_buf = mbuf->payload;
+	mbuf->header = mbuf->payload;
+	mbuf->alloc_len = 1024;
+
+	memset( src_buf, 0, sizeof( src_buf ) );
+	rmr_bytes2payload( mbuf, NULL, strlen( src_buf) );				// errno should be set on return
+	errors += fail_if( errno == 0, "buf copy to payload with nil src returned good errno" );
+
+	rmr_bytes2payload( NULL, src_buf, strlen( src_buf) );			// errno should be set on return
+	errors += fail_if( errno == 0, "buf copy to payload with nil mbuf returned good errno" );
+
+	mbuf->state = 1;											// force it to something to test that it was set
+	rmr_bytes2payload( mbuf, src_buf, strlen( src_buf) );
+	errors += fail_if( mbuf->state != RMR_OK, "buf copy to payload returned bad state in mbuf" );
+
+	rmr_bytes2payload( mbuf, src_buf, 8192 );						// bust the limit
+	errors += fail_if( mbuf->state == RMR_OK, "huge buf copy to payload returned good state in mbuf" );
+	errors += fail_if( errno == 0, "huge buf copy to payload returned good state in errno" );
+	
+
+	snprintf( src_buf, sizeof( src_buf ), "This is some text in the buffer" );
+	rmr_str2payload( mbuf, src_buf );							// this uses bytes2payload, so only one invocation needed
+
+	errno = 0;
+	i = rmr_bytes2meid( NULL, src_buf, RMR_MAX_MEID );
+	errors += fail_if( errno == 0, "(errno) attempt to copy bytes to meid with nil message" );
+	errors += fail_if( i > 0, "(rv) attempt to copy bytes to meid with nil message" );
+
+	errno = 0;
+	i = rmr_bytes2meid( mbuf, NULL, RMR_MAX_MEID );
+	errors += fail_if( errno == 0, "(errno) attempt to copy bytes to meid with nil source buffer" );
+	errors += fail_if( i > 0, "(rv) attempt to copy bytes to meid with nil message" );
+
+	errno = 0;
+	i = rmr_bytes2meid( mbuf, src_buf, RMR_MAX_MEID + 1 );
+	errors += fail_if( errno == 0, "(errno) attempt to copy bytes to meid with large source buffer" );
+	errors += fail_if( i != RMR_MAX_MEID, "(rv) attempt to copy bytes to meid with large source buffer" );
+
+	errno = 0;
+	i = rmr_bytes2meid( mbuf, src_buf, RMR_MAX_MEID  );
+	errors += fail_if( errno != 0, "copy bytes to meid; expected errno to be ok" );
+	errors += fail_if( i != RMR_MAX_MEID, "copy bytes to meid; expected return value to be max meid len" );
+
+
+
+	errno = 0;
+	snprintf( src_buf, sizeof( src_buf ), "meid-fits" );
+	i = rmr_str2meid( NULL, src_buf );
+	errors += fail_if( errno == 0, "(errno) attempt to copy string to meid with nil message" );
+	errors += fail_if( i == RMR_OK, "(rv) attempt to copy string to meid with nil message" );
+
+	errno = 0;
+	i = rmr_str2meid( mbuf, NULL );
+	errors += fail_if( errno == 0, "(errno) attempt to copy string to meid with nil source buffer" );
+	errors += fail_if( i == RMR_OK, "(rv) attempt to copy string to meid with nil message" );
+
+	errno = 0;
+	i = rmr_str2meid( mbuf, src_buf );
+	errors += fail_if( errno != 0, "copy string to meid; expected errno to be ok" );
+	errors += fail_if( i != RMR_OK, "copy string to meid; expected return value to be RMR_OK" );
+
+	errno = 0;
+	snprintf( src_buf, sizeof( src_buf ), "meid-should-be-too-large-to-fit-in-the-meid" );
+	i = rmr_str2meid( mbuf, src_buf );
+	errors += fail_if( errno == 0, "(errno) attempt to copy string to meid with large source buffer" );
+	errors += fail_if( i == RMR_OK, "(rv) attempt to copy string to meid with large source buffer" );
+
+
+	errno = 0;
+	i = rmr_bytes2xact( NULL, src_buf, RMR_MAX_XID );
+	errors += fail_if( errno == 0, "(errno) attempt to copy bytes to xact with nil message" );
+	errors += fail_if( i > 0, "(rv) attempt to copy bytes to xact with nil message" );
+
+	errno = 0;
+	i = rmr_bytes2xact( mbuf, NULL, RMR_MAX_XID );
+	errors += fail_if( errno == 0, "(errno) attempt to copy bytes to xact with nil source buffer" );
+	errors += fail_if( i > 0, "(rv) attempt to copy bytes to xact with nil message" );
+
+	errno = 0;
+	i = rmr_bytes2xact( mbuf, src_buf, RMR_MAX_XID + 1 );
+	errors += fail_if( errno == 0, "(errno) attempt to copy bytes to xact with large source buffer" );
+	errors += fail_if( i != RMR_MAX_XID, "(rv) attempt to copy bytes to xact with large source buffer" );
+
+	errno = 0;
+	i = rmr_bytes2xact( mbuf, src_buf, RMR_MAX_XID  );
+	errors += fail_if( errno != 0, "copy bytes to xact; expected errno to be ok" );
+	errors += fail_if( i != RMR_MAX_XID, "copy bytes to xact; expected return value to be max xact len" );
+
+
+
+	errno = 0;
+	snprintf( src_buf, sizeof( src_buf ), "xact-fits" );
+	i = rmr_str2xact( NULL, src_buf );
+	errors += fail_if( errno == 0, "(errno) attempt to copy string to xact with nil message" );
+	errors += fail_if( i == RMR_OK, "(rv) attempt to copy string to xact with nil message" );
+
+	errno = 0;
+	i = rmr_str2xact( mbuf, NULL );
+	errors += fail_if( errno == 0, "(errno) attempt to copy string to xact with nil source buffer" );
+	errors += fail_if( i == RMR_OK, "(rv) attempt to copy string to xact with nil message" );
+
+	errno = 0;
+	i = rmr_str2xact( mbuf, src_buf );
+	errors += fail_if( errno != 0, "copy string to xact; expected errno to be ok" );
+	errors += fail_if( i != RMR_OK, "copy string to xact; expected return value to be RMR_OK" );
+
+	errno = 0;
+	snprintf( src_buf, sizeof( src_buf ), "xact-should-be-too-large-to-fit-in-the-xact" );
+	i = rmr_str2xact( mbuf, src_buf );
+	errors += fail_if( errno == 0, "(errno) attempt to copy string to xact with large source buffer" );
+	errors += fail_if( i == RMR_OK, "(rv) attempt to copy string to xact with large source buffer" );
+
+	
+	snprintf( src_buf, sizeof( src_buf ), "test-meid" );
+	rmr_str2meid( mbuf, src_buf );
+
+	errno = 0;
+	c = rmr_get_meid( NULL, NULL );
+	errors += fail_if( c != NULL, "get meid with nil message buffer" );
+	errors += fail_if( errno == 0, "(errno bad) get meid with nil msg buffer" ); 
+
+	
+	c = rmr_get_meid( mbuf, NULL );			// should allocate and return c
+	errors += fail_if( c == NULL, "get meid with nil dest pointer (did not allocate a buffer)" );
+	errors += fail_if( strcmp( c, "test-meid" ) != 0, "did not get expected meid from mbuffer" );
+
+	c = rmr_get_meid( mbuf, c );
+	errors += fail_if( c == NULL, "get meid with a dest pointer returned no pointer" );
+	errors += fail_if( strcmp( c, "test-meid" ) != 0, "did not get expected meid from mbuffer" );
+	
+
+	return errors > 0;			// overall exit code bad if errors
+}
diff --git a/test/mbuf_api_test.c b/test/mbuf_api_test.c
new file mode 100644
index 0000000..858efdc
--- /dev/null
+++ b/test/mbuf_api_test.c
@@ -0,0 +1,58 @@
+// : vi ts=4 sw=4 noet :
+/*
+==================================================================================
+        Copyright (c) 2019 Nokia
+        Copyright (c) 2018-2019 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
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+
+/*
+	Mnemonic:	mbuf_api_test.c
+	Abstract:	Unit tests for the mbuf common API functions.
+	Author:		E. Scott Daniels
+	Date:		2 April 2019
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <ctype.h>
+
+
+#include "../src/common/include/rmr.h"
+#include "../src/common/include/rmr_agnostic.h"
+
+#include "../src/common/src/mbuf_api.c"			// module under test
+
+#include "test_support.c"						// our private library of test tools
+#include "mbuf_api_static_test.c"				// test functions
+
+int main( ) {
+	int errors = 0;
+
+	errors += mbuf_api_test( );
+
+	if( errors ) {
+		fprintf( stderr, "<FAIL> mbuf_api tests failed\n" );
+	} else {
+		fprintf( stderr, "<OK>	 mbuf_api tests pass\n" );
+	}
+}
diff --git a/test/ring_static_test.c b/test/ring_static_test.c
new file mode 100644
index 0000000..482244c
--- /dev/null
+++ b/test/ring_static_test.c
@@ -0,0 +1,164 @@
+// : vi ts=4 sw=4 noet :
+/*
+==================================================================================
+        Copyright (c) 2019 Nokia 
+        Copyright (c) 2018-2019 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
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+	Mmemonic:	ring_static_test.c
+	Abstract:	Test the ring funcitons. These are meant to be included at compile
+				time by the test driver.  
+
+	Author:		E. Scott Daniels
+	Date:		3 April 2019
+*/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "../src/common/include/rmr.h"
+#include "../src/common/include/rmr_agnostic.h"
+//#include "../src/common/src/ring_static.c"
+
+
+/*
+	Conduct a series of interleaved tests inserting i-factor 
+	values before beginning to pull values (i-factor must be
+	size - 2 smaller than the ring. 
+	Returns 0 on success, 1 on insert failure and 2 on pull failure.
+*/
+static int ie_test( void* r, int i_factor, long inserts ) {
+	int i;
+	int* dp;
+	int data[29];
+
+	for( i = 0; i < inserts; i++ ) {
+		data[i%29] = i;
+		if( ! uta_ring_insert( r, &data[i%29] ) ) {
+			fprintf( stderr, "<FAIL> interleaved insert failed on ifactor=%d i=%d\n", i_factor, i );
+			return 1;
+		}
+		if( i > i_factor-1 ) {
+			dp = uta_ring_extract( r );
+			if( *dp != data[(i-i_factor)%29] ) {
+				fprintf( stderr, "<FAIL> interleaved exctract failed on ifactor=%d i=%d expected=%d got=%d\n", i_factor, i, data[(i-i_factor)%29], *dp );
+				return 2;
+			}
+		}
+	}
+	//fprintf( stderr, "<OK>   interleaved insert/extract test passed for insert factor %d\n", i_factor );
+
+	return 0;
+}
+
+static int ring_test( ) {
+	void* r;
+	int i;
+	int j;
+	int	data[20];
+	int*	dp;
+	int size = 18;
+
+	r = uta_mk_ring( 0 );			// should return nil
+	if( r != NULL ) {
+		fprintf( stderr, "<FAIL> attempt to make a ring with size 0 returned a pointer\n" );
+		return 1;
+	}
+	r = uta_mk_ring( -1 );			// should also return nil
+	if( r != NULL ) {
+		fprintf( stderr, "<FAIL> attempt to make a ring with size <0 returned a pointer\n" );
+		return 1;
+	}
+
+	r = uta_mk_ring( 18 );
+	if( r == NULL ) {
+		fprintf( stderr, "<FAIL> unable to make ring with 17 entries\n" );
+		return 1;
+	}
+
+	for( i = 0; i < 20; i++ ) {		// test to ensure it reports full when head/tail start at 0
+		data[i] = i;
+		if( ! uta_ring_insert( r, &data[i] ) ) {
+			break;
+		}
+	}
+
+	if( i > size ) {
+		fprintf( stderr, "<FAIL> didn not report table full: i=%d\n", i );
+		return 1;
+	}
+
+	fprintf( stderr, "<OK>   reported table full at i=%d as expected\n", i );
+
+
+	for( i = 0; i < size + 3; i++ ) {								// ensure they all come back in order, and we don't get 'extras'
+		if( (dp = uta_ring_extract( r )) == NULL ) {
+			if( i < size-1 ) {
+				fprintf( stderr, "<FAIL> nil pointer at i=%d\n", i );
+				return 1;
+			} else {
+				break;
+			}
+		}	
+
+		if( *dp != i ) {
+			fprintf( stderr, "<FAIL> data at i=% isnt right; expected %d got %d\n", i, i, *dp );
+		}
+	}
+	if( i > size ) {
+		fprintf( stderr, "<FAIL> got too many values on extract: %d\n", i );
+		return 1;
+	}
+	fprintf( stderr, "<OK>   extracted values were sane, got: %d\n", i-1 );
+		
+	uta_ring_free( NULL );							// ensure this doesn't blow up
+	uta_ring_free( r );
+	for( i = 2; i < 15; i++ ) {
+		r = uta_mk_ring( 16 );
+		if( ie_test( r, i, 101 ) != 0 ) {			// modest number of inserts
+			fprintf( stderr, "<FAIL> ie test for 101 inserts didn't return 0\n" );
+			return 1;
+		}
+
+		uta_ring_free( r );
+	}
+	fprintf( stderr, "<OK>   all modest insert/exctract tests pass\n" );
+
+	size = 5;
+	for( j = 0; j < 20; j++ ) {
+		for( i = 2; i < size - 2; i++ ) {
+			r = uta_mk_ring( size );
+			if( ie_test( r, i, 66000 ) != 0 ) {			// should force the 16bit head/tail indexes to roll over
+				fprintf( stderr, "<FAIL> ie test for 66K inserts didn't return 0\n" );
+				return 1;
+			}
+	
+			uta_ring_free( r );
+		}
+		fprintf( stderr, "<OK>   all large insert/exctract tests pass ring size=%d\n", size );
+
+		size++;
+	}
+
+	fprintf( stderr, "<INFO> all ring tests pass\n" );
+	return 0;
+}
diff --git a/test/ring_test.c b/test/ring_test.c
index 331f976..a06668f 100644
--- a/test/ring_test.c
+++ b/test/ring_test.c
@@ -20,9 +20,12 @@
 
 /*
 	Mmemonic:	ring_test.c
-	Abstract:	Test the ring funcitons.
+	Abstract:	This is a stand alone test driver for the ring module. It 
+				includes the static tests after setting up the environment
+				then invokes it.
+
 	Author:		E. Scott Daniels
-	Date:		31 July 2017
+	Date:		3 April 2019
 */
 
 #include <unistd.h>
@@ -37,123 +40,19 @@
 #include "../src/common/include/rmr_agnostic.h"
 #include "../src/common/src/ring_static.c"
 
+#include "test_support.c"					// things like fail_if()
+#include "ring_static_test.c"				// the actual tests
 
-/*
-	Conduct a series of interleaved tests inserting i-factor 
-	values before beginning to pull values (i-factor must be
-	size - 2 smaller than the ring. 
-	Returns 0 on success, 1 on insert failure and 2 on pull failure.
-*/
-static int ie_test( void* r, int i_factor, long inserts ) {
-	int i;
-	int* dp;
-	int data[29];
+int main( ) {
+	int errors = 0;
 
-	for( i = 0; i < inserts; i++ ) {
-		data[i%29] = i;
-		if( ! uta_ring_insert( r, &data[i%29] ) ) {
-			fprintf( stderr, "[FAIL] interleaved insert failed on ifactor=%d i=%d\n", i_factor, i );
-			return 1;
-		}
-		if( i > i_factor-1 ) {
-			dp = uta_ring_extract( r );
-			if( *dp != data[(i-i_factor)%29] ) {
-				fprintf( stderr, "[FAIL] interleaved exctract failed on ifactor=%d i=%d expected=%d got=%d\n", i_factor, i, data[(i-i_factor)%29], *dp );
-				return 2;
-			}
-		}
-	}
-	//fprintf( stderr, "[OK]   interleaved insert/extract test passed for insert factor %d\n", i_factor );
+	errors += ring_test( );
 
-	return 0;
-}
-
-int main( void  ) {
-	void* r;
-	int i;
-	int j;
-	int	data[20];
-	int*	dp;
-	int size = 18;
-
-	r = uta_mk_ring( 0 );			// should return nil
-	if( r != NULL ) {
-		fprintf( stderr, "[FAIL] attempt to make a ring with size 0 returned a pointer\n" );
-		exit( 1 );
-	}
-	r = uta_mk_ring( -1 );			// should also return nil
-	if( r != NULL ) {
-		fprintf( stderr, "[FAIL] attempt to make a ring with size <0 returned a pointer\n" );
-		exit( 1 );
+	if( errors ) {
+		fprintf( stderr, "<FAIL> ring tests failed\n" );
+	} else {
+		fprintf( stderr, "<OK>	 ring tests pass\n" );
 	}
 
-	r = uta_mk_ring( 18 );
-	if( r == NULL ) {
-		fprintf( stderr, "[FAIL] unable to make ring with 17 entries\n" );
-		exit( 1 );
-	}
-
-	for( i = 0; i < 20; i++ ) {		// test to ensure it reports full when head/tail start at 0
-		data[i] = i;
-		if( ! uta_ring_insert( r, &data[i] ) ) {
-			break;
-		}
-	}
-
-	if( i > size ) {
-		fprintf( stderr, "[FAIL] didn not report table full: i=%d\n", i );
-		exit( 1 );
-	}
-
-	fprintf( stderr, "[OK]   reported table full at i=%d as expected\n", i );
-
-
-	for( i = 0; i < size + 3; i++ ) {								// ensure they all come back in order, and we don't get 'extras'
-		if( (dp = uta_ring_extract( r )) == NULL ) {
-			if( i < size-1 ) {
-				fprintf( stderr, "[FAIL] nil pointer at i=%d\n", i );
-				exit( 1 );
-			} else {
-				break;
-			}
-		}	
-
-		if( *dp != i ) {
-			fprintf( stderr, "[FAIL] data at i=% isnt right; expected %d got %d\n", i, i, *dp );
-		}
-	}
-	if( i > size ) {
-		fprintf( stderr, "[FAIL] got too many values on extract: %d\n", i );
-		exit( 1 );
-	}
-	fprintf( stderr, "[OK]   extracted values were sane, got: %d\n", i-1 );
-		
-	uta_ring_free( NULL );							// ensure this doesn't blow up
-	uta_ring_free( r );
-	for( i = 2; i < 15; i++ ) {
-		r = uta_mk_ring( 16 );
-		if( ie_test( r, i, 101 ) != 0 ) {			// modest number of inserts
-			exit( 1 );
-		}
-
-		uta_ring_free( r );
-	}
-	fprintf( stderr, "[OK]   all modest insert/exctract tests pass\n" );
-
-	size = 5;
-	for( j = 0; j < 20; j++ ) {
-		for( i = 2; i < size - 2; i++ ) {
-			r = uta_mk_ring( size );
-			if( ie_test( r, i, 66000 ) != 0 ) {			// should force the 16bit head/tail indexes to roll over
-				exit( 1 );
-			}
-	
-			uta_ring_free( r );
-		}
-		fprintf( stderr, "[OK]   all large insert/exctract tests pass ring size=%d\n", size );
-
-		size++;
-	}
-
-	return 0;
+	return errors;
 }
diff --git a/test/rmr_nng_api_static_test.c b/test/rmr_nng_api_static_test.c
new file mode 100644
index 0000000..e9ecb72
--- /dev/null
+++ b/test/rmr_nng_api_static_test.c
@@ -0,0 +1,274 @@
+// : vi ts=4 sw=4 noet :
+/*
+==================================================================================
+        Copyright (c) 2019 Nokia 
+        Copyright (c) 2018-2019 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
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+	Mmemonic:	rmr_api_static_test.c
+	Abstract:	Specific tests related to the API functions in rmr_nng.c/rmr.c.
+				This should be included by a driver, but only the main RMr
+				driver and there likely not be a specific stand alone driver
+				for just this small set of tests because of the depth of the 
+				library needed to test at this level.
+
+				The message buffer specific API tests are in a different static
+				module.  API functions tested here are:
+					 rmr_close
+					 rmr_get_rcvfd
+					 rmr_ready
+					 rmr_init
+					 rmr_set_rtimeout
+					 rmr_set_stimeout
+					 rmr_rcv_specific
+					 rmr_torcv_msg
+					 rmr_rcv_msg
+					 rmr_call
+					 rmr_rts_msg
+					 rmr_send_msg
+					 rmr_mtosend_msg
+					 rmr_free_msg
+
+	Author:		E. Scott Daniels
+	Date:		5 April 2019
+*/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "../src/common/include/rmr.h"
+#include "../src/common/include/rmr_agnostic.h"
+//#include "../src/common/src/ring_static.c"
+
+/*
+	Send a 'burst' of messages to drive some send retry failures to increase RMr coverage
+	by handling the retry caee.
+*/
+static void send_n_msgs( void* ctx, int n ) {
+	rmr_mbuf_t*	msg;			// message buffers
+	int i;
+
+	msg = rmr_alloc_msg( ctx,  1024 );
+	if( ! msg ) {
+		return;
+	}
+
+	for( i = 0; i < n; i++ ) {
+fprintf( stderr, "mass send\n" );
+		msg->len = 100;
+		msg->mtype = 1;
+		msg->state = 999;
+		errno = 999;
+		msg = rmr_send_msg( ctx, msg );
+	}
+}
+
+static int rmr_api_test( ) {
+	int		errors = 0;
+	void*	rmc;				// route manager context
+	void*	rmc2;				// second context for non-listener init
+	rmr_mbuf_t*	msg;			// message buffers
+	rmr_mbuf_t*	msg2;
+	int		v = 0;					// some value
+	char	wbuf[128];
+	int		i;
+
+	v = rmr_ready( NULL );
+	errors += fail_if( v != 0, "rmr_ready returned true before initialisation" );
+
+	if( (rmc = rmr_init( "4560", 1024, FL_NOTHREAD )) == NULL ) {
+		fail_if_nil( rmc, "rmr_init returned a nil pointer" );
+		return 1;
+	}
+
+	if( (rmc2 = rmr_init( ":6789", 1024, FL_NOTHREAD )) == NULL ) {		// init without starting a thread
+		errors += fail_if_nil( rmc, "rmr_init returned a nil pointer for non-threaded init" );
+	}
+
+	free_ctx( rmc2 );			// coverage
+
+	if( (rmc2 = rmr_init( NULL, 1024, FL_NOTHREAD )) == NULL ) {			// drive default port selector code
+		errors += fail_if_nil( rmc, "rmr_init returned a nil pointer when driving for default port" );
+	}
+
+	v = rmr_ready( rmc );		// unknown return; not checking at the moment
+
+	msg = rmr_alloc_msg( NULL,  1024 );									// should return nil pointer
+	errors += fail_not_nil( msg, "rmr_alloc_msg didn't return nil when given nil context" );
+
+	msg = rmr_alloc_msg( rmc, 2048 );				// allocate larger than default size given on init
+	errors += fail_if_nil( msg, "rmr_alloc_msg returned nil msg pointer" );
+
+	v = rmr_payload_size( NULL );
+	errors += fail_if( v >= 0, "rmr_payload_size returned valid size for nil message" );
+	errors += fail_if( errno == 0, "rmr_payload_size did not set errno on failure" );
+
+	v = rmr_payload_size( msg );
+	if( v >= 0 ) {
+		errors += fail_not_equal( v, 2048, "rmr_payload_size returned invalid size (a) instead of expected size (b)" );
+		errors += fail_if( errno != 0, "rmr_payload_size did not clear errno on success" );
+	} else {
+		errors += fail_if( v < 0, "rmr_payload_size returned invalid size for good message" );
+	}
+	
+	v = rmr_get_rcvfd( NULL );
+	errors += fail_if( v >= 0, "rmr_get_rcvfd returned a valid file descriptor when given nil context" );
+	v = rmr_get_rcvfd( rmc );
+	errors += fail_if( v < 0, "rmr_get_rcvfd did not return a valid file descriptor" );
+
+	msg2 = rmr_send_msg( NULL, NULL );			// drive for coverage
+	errors += fail_not_nil( msg2, "send_msg returned msg pointer when given a nil message and context" );
+
+	// --- sends will fail with a no endpoint error until a dummy route table is set, so we test fail case first.
+	msg->len = 100;
+	msg->mtype = 1;
+	msg->state = 999;
+	errno = 999;
+	msg = rmr_send_msg( rmc, msg );
+	errors += fail_if_nil( msg, "send_msg_ did not return a message on send" );
+	if( msg ) {
+		errors += fail_not_equal( msg->state, RMR_ERR_NOENDPT, "send_msg did not return no endpoints before rtable added" );
+		errors += fail_if( errno == 0, "send_msg did not set errno" );
+	}
+
+	gen_rt( rmc );		// --- after this point there is a dummy route table so send and rts calls should be ok
+
+	msg->len = 100;
+	msg->mtype = 1;
+	msg->state = 999;
+	errno = 999;
+	msg = rmr_send_msg( rmc, msg );
+	errors += fail_if_nil( msg, "send_msg_ did not return a message on send" );
+	if( msg ) {
+		errors += fail_not_equal( msg->state, RMR_OK, "send_msg returned bad status for send that should work" );
+		errors += fail_if( errno != 0, "send_msg set errno for send that should work" );
+		v = rmr_payload_size( msg );
+		errors += fail_if( v != 2048, "send_msg did not allocate new buffer with correct size" );
+	}
+
+	rmr_set_stimeout( NULL, 0 );
+	rmr_set_stimeout( rmc, 20 );
+	rmr_set_stimeout( rmc, -1 );
+	rmr_set_rtimeout( NULL, 0 );
+	rmr_set_rtimeout( rmc, 20 );
+	rmr_set_rtimeout( rmc, -1 );
+
+	msg2 = rmr_rcv_msg( NULL, NULL );
+	errors += fail_if( msg2 != NULL, "rmr_rcv_msg returned msg when given nil context and msg" );
+
+	msg2 = rmr_rcv_msg( rmc, NULL );
+	errors += fail_if( msg2 == NULL, "rmr_rcv_msg returned nil msg when given nil msg" );
+	if( msg2 ) {
+		errors += fail_not_equal( msg2->state, RMR_OK, "receive given nil message did not return msg with good state" );
+	}
+
+	msg = rmr_rcv_msg( rmc, msg );
+	if( msg ) {
+		errors += fail_if( msg->state != RMR_OK, "rmr_rcv_msg did not return an ok state" );
+		errors += fail_not_equal( msg->len, 129, "rmr_rcv_msg returned message with invalid len" );
+	} else {
+		errors += fail_if_nil( msg, "rmr_rcv_msg returned a nil pointer" );
+	}
+
+	rmr_rts_msg( NULL, NULL );			// drive for coverage
+	rmr_rts_msg( rmc, NULL );
+	errors += fail_if( errno == 0, "rmr_rts_msg did not set errno when given a nil message" );
+
+	msg = rmr_rts_msg( rmc, msg );			// return the buffer to the sender
+	errors += fail_if_nil( msg, "rmr_rts_msg did not return a message pointer" );
+	errors += fail_if( errno != 0, "rmr_rts_msg did not reset errno" );
+
+
+	snprintf( msg->xaction, 17, "%015d", 16 );		// dummy transaction id (emulation generates, this should arrive after a few calls to recv)
+	msg = rmr_call( rmc, msg );						// this call should return a message as we can anticipate a dummy message in
+	errors += fail_if_nil( msg, "rmr_call returned a nil message on call expected to succeed" );
+	if( msg ) {
+		errors += fail_not_equal( msg->state, RMR_OK, "rmr_call did not properly set state on successful return" );
+		errors += fail_if( errno != 0, "rmr_call did not properly set errno on successful return" );
+	}
+
+	snprintf( wbuf, 17, "%015d", 14 );				// if we call receive we should find this in the first 15 tries
+	for( i = 0; i < 16; i++ ) {
+		msg = rmr_rcv_msg( rmc, msg );
+		if( msg ) {
+			if( strcmp( wbuf, msg->xaction ) == 0 ) {		// found the queued message
+				break;
+			}
+			fprintf( stderr, "<INFO> msg: %s\n", msg->xaction );
+		} else {
+			errors += fail_if_nil( msg, "receive returnd nil msg while looking for queued message" );
+		}
+	}
+
+	errors += fail_if( i >= 16, "did not find expected message on queue" );
+		
+	if( ! msg ) {
+		msg = rmr_alloc_msg( rmc, 2048 );				// something buggered above; get a new one
+	}
+	msg = rmr_call( rmc, msg );							// make a call that we never expect a response on
+	errors += fail_not_nil( msg, "rmr_call returned a non-nil message on call expected not to receive a response" );
+	if( msg ) {
+		errors += fail_not_equal( msg->state, RMR_OK, "rmr_call did not properly set state on queued message receive" );
+		errors += fail_if( errno != 0, "rmr_call did not properly set errno on queued message receivesuccessful" );
+	}
+
+	msg = rmr_call( rmc, msg );						// this should "timeout" because the message xaction id won't ever appear again
+	errors += fail_not_nil( msg, "rmr_call returned a non-nil message on call expected to fail" );
+	errors += fail_if( errno == 0, "rmr_call did not set errno on failure" );
+
+	rmr_free_msg( NULL ); 			// drive for coverage; nothing to check
+	rmr_free_msg( msg2 );
+
+
+	// ---  test timeout receive; our dummy epoll function will return 1 ready on first call and 0 ready (timeout emulation) on second
+	// 		however we must drain the swamp (queue) first, so run until we get a timeout error, or 20 and report error if we get to 20.
+	msg = NULL;
+	for( i = 0; i < 40; i++ ) {
+		msg = rmr_torcv_msg( rmc, msg, 10 );
+		errors += fail_if_nil( msg, "torcv_msg returned nil msg when message expected" );
+		if( msg ) {
+			if( msg->state == RMR_ERR_TIMEOUT ) {			// queue drained and we've seen both states from poll if we get a timeout
+				break;
+			}
+		}
+	}
+	errors += fail_if( i >= 40, "torcv_msg never returned a timeout" );
+
+	
+	em_send_failures = 1;
+	send_n_msgs( rmc, 30 );			// send 30 messages with emulation failures
+	em_send_failures = 0;
+
+	
+	rmr_close( NULL );			// drive for coverage
+	rmr_close( rmc );			// no return to check; drive for coverage
+
+//extern rmr_mbuf_t* rmr_mtosend_msg( void* vctx, rmr_mbuf_t* msg, int max_to ) {
+//extern rmr_mbuf_t* rmr_torcv_msg( void* vctx, rmr_mbuf_t* old_msg, int ms_to ) {
+
+
+
+	if( ! errors ) {
+		fprintf( stderr, "<INFO> all RMr API tests pass\n" );
+	}
+	return !!errors;
+}
diff --git a/test/rmr_nng_test.c b/test/rmr_nng_test.c
new file mode 100644
index 0000000..1ebdb94
--- /dev/null
+++ b/test/rmr_nng_test.c
@@ -0,0 +1,130 @@
+// :vi sw=4 ts=4 noet:
+/*
+==================================================================================
+	Copyright (c) 2019 Nokia
+	Copyright (c) 2018-2019 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
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+	Mmemonic:	rmr_nng_test.c
+	Abstract:	This tests the whole rmr nng implementation. This driver
+				includes all of the module specific unit test static files
+				(e.g. wormhole_static_test.c) and drives the tests contained
+				there.   The individual modules allow them to be driven by
+				a standalone driver, and to be maintained separately. We must
+				test by inclusion because of the static nature of the internal
+				functions of the library.
+
+	Author:		E. Scott Daniels
+	Date:		18 January 2018		(IMO HRTL)
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <ctype.h>
+
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <sys/epoll.h>
+
+#define DEBUG 1
+
+#include <nng/nng.h>
+#include <nng/protocol/pubsub0/pub.h>
+#include <nng/protocol/pubsub0/sub.h>
+#include <nng/protocol/pipeline0/push.h>
+#include <nng/protocol/pipeline0/pull.h>
+
+#define EMULATE_NNG
+#include "test_nng_em.c"							// nng/nn emulation (before including things under test)
+
+
+#include "../src/common/include/rmr.h"					// things the users see
+#include "../src/common/include/rmr_symtab.h"
+#include "../src/common/include/rmr_agnostic.h"			// transport agnostic header
+#include "../src/nng/include/rmr_nng_private.h"			// transport specific
+
+#include "../src/common/src/symtab.c"
+#include "../src/nng/src/rmr_nng.c"
+
+static void gen_rt( uta_ctx_t* ctx );		// defined in sr_nng_static_test, but used by a few others (eliminate order requirement below)
+
+											// specific test tools in this directory
+#include "test_support.c"					// things like fail_if()
+											// and finally....
+#include "tools_static_test.c"				// local test functions pulled directly because of static nature of things
+#include "symtab_static_test.c"
+#include "ring_static_test.c"
+#include "rt_static_test.c"
+#include "sr_nng_static_test.c"
+#include "wormhole_static_test.c"
+#include "rmr_nng_api_static_test.c"
+
+
+/*
+	Drive each of the separate tests and report.
+*/
+int main() {
+	int errors = 0;
+
+	fprintf( stderr, "<INFO> starting tool tests\n" );
+	errors += tools_test();
+	fprintf( stderr, "<INFO> error count: %d\n", errors );
+
+	fprintf( stderr, "<INFO> starting ring tests (%d)\n", errors );
+	errors += ring_test();
+	fprintf( stderr, "<INFO> error count: %d\n", errors );
+
+	fprintf( stderr, "<INFO> starting symtab tests\n" );
+	errors += symtab_test( );
+	fprintf( stderr, "<INFO> error count: %d\n", errors );
+
+	fprintf( stderr, "<INFO> starting rtable tests\n" );
+	errors += rt_test();				// route table tests
+	fprintf( stderr, "<INFO> error count: %d\n", errors );
+
+	fprintf( stderr, "<INFO> starting RMr API tests\n" );
+	errors += rmr_api_test();
+	fprintf( stderr, "<INFO> error count: %d\n", errors );
+
+	fprintf( stderr, "<INFO> starting wormhole tests\n" );
+	errors += worm_test();				// test wormhole funcitons
+	fprintf( stderr, "<INFO> error count: %d\n", errors );
+
+	fprintf( stderr, "<INFO> starting send/receive tests\n" );
+	errors += sr_nng_test();				// test the send/receive static functions
+	fprintf( stderr, "<INFO> error count: %d\n", errors );
+
+	if( errors == 0 ) {
+		fprintf( stderr, "<PASS> all tests were OK\n" );
+	} else {
+		fprintf( stderr, "<FAIL> %d modules reported errors\n", errors );
+	}
+
+	return !!errors;
+}
diff --git a/test/rt_static_test.c b/test/rt_static_test.c
new file mode 100644
index 0000000..f1e6981
--- /dev/null
+++ b/test/rt_static_test.c
@@ -0,0 +1,231 @@
+// : vi ts=4 sw=4 noet :
+/*
+==================================================================================
+        Copyright (c) 2019 Nokia 
+        Copyright (c) 2018-2019 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
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+	Mmemonic:	rt_static_test.c
+	Abstract:	Test the route table funcitons. These are meant to be included at compile
+				time by the test driver.  
+
+	Author:		E. Scott Daniels
+	Date:		3 April 2019
+*/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "../src/common/include/rmr.h"
+#include "../src/common/include/rmr_agnostic.h"
+
+typedef struct entry_info {
+	int group;
+	char* ep_name;
+} ei_t;
+
+
+/*
+	This is the main route table test. It sets up a very specific table
+	for testing (not via the generic setup function for other test
+	situations).
+*/
+static int rt_test( ) {
+	uta_ctx_t* ctx;			// context needed to test load static rt
+	route_table_t* rt;		// route table
+	route_table_t* crt;		// cloned route table
+	rtable_ent_t*	rte;	// entry in the table
+	endpoint_t*	ep;			// endpoint added
+	int more = 0;			// more flag from round robin
+	int errors = 0;			// number errors found
+	int	i;
+	int k;
+	int mtype;
+	int value;
+	int alt_value;
+	ei_t	entries[50];	// end point information
+	int		gcounts[5];		// number of groups in this set
+	int		ecounts[5];		// number of elements per group
+	int		mtypes[5];		// msg type for each group set
+	char*	tok;
+	char*	nxt_tok;
+	int		enu = 0;
+	int		state;
+	char	*buf;
+	char*	seed_fname;		// seed file
+	nng_socket nn_sock;		// this is a struct in nng, so difficult to validate
+
+	setenv( "ENV_VERBOSE_FILE", ".ut_rmr_verbose", 1 );			// allow for verbose code in rtc to be driven
+	i = open( ".rmr_verbose", O_CREAT, 0664 );
+	if( i >= 0 ) {
+		close( i );
+	}
+
+	gcounts[0] = 1;			// build entry info -- this is hackish, but saves writing another parser
+	ecounts[0] = 2;
+	mtypes[0] = 0;
+	entries[enu].group = 0; entries[enu].ep_name = "yahoo.com:4561"; enu++;		// use a dns resolvable name to test that
+	entries[enu].group = 0; entries[enu].ep_name = "localhost:4562"; enu++;	// rest can default to some dummy ip
+
+	gcounts[1] = 2;
+	ecounts[1] = 3;
+	mtypes[1] = 1;
+	entries[enu].group = 0; entries[enu].ep_name = "localhost:4561"; enu++;
+	entries[enu].group = 0; entries[enu].ep_name = "localhost:4568"; enu++;
+	entries[enu].group = 0; entries[enu].ep_name = "localhost:4569"; enu++;
+
+	gcounts[2] = 0;		// 0 groups means use same rte, this is the next gropup
+	ecounts[2] = 2;
+	mtypes[2] = 1;
+	entries[enu].group = 1; entries[enu].ep_name = "localhost:4561"; enu++;
+	entries[enu].group = 1; entries[enu].ep_name = "localhost:4562"; enu++;
+
+	gcounts[3] = 1;		// 0 groups means use same rte, this is the next gropup
+	ecounts[3] = 2;
+	mtypes[3] = 2;
+	entries[enu].group = 0; entries[enu].ep_name = "localhost:4563"; enu++;
+	entries[enu].group = 0; entries[enu].ep_name = "localhost:4564"; enu++;
+
+	gcounts[4] = 1;		// 0 groups means use same rte, this is the next gropup
+	ecounts[4] = 1;
+	mtypes[4] = 3;
+	entries[enu].group = 0; entries[enu].ep_name = "localhost:4565"; enu++;
+
+
+
+	rt = uta_rt_init( );										// get us a route table
+	if( (errors += fail_if_nil( rt, "pointer to route table" )) ) {
+		fprintf( stderr, "<FAIL> abort: cannot continue without a route table\n" );
+		exit( 1 );
+	}
+
+	enu = 0;
+	rte = NULL;
+	for( i = 0; i < sizeof( gcounts )/sizeof( int ); i++ ) {				// add entries defined above
+		if( gcounts[i] ) {
+			rte = uta_add_rte( rt, mtypes[i], gcounts[i] );					// get/create entry for message type
+			if( (errors += fail_if_nil( rte, "route table entry" )) ) {
+				fprintf( stderr, "<FAIL> abort: cannot continue without a route table entry\n" );
+				exit( 1 );
+			}
+		} else {
+			if( rte == NULL ) {
+				fprintf( stderr, "<SNAFU> internal testing error -- rte was nil for gcount == 0\n" );
+				exit( 1 );
+			}
+		}
+
+		for( k = 0; k < ecounts[i]; k++ ) {
+			ep = uta_add_ep( rt, rte, entries[enu].ep_name, entries[enu].group );
+			errors += fail_if_nil( ep, "endpoint" );
+			enu++;
+		}
+	}
+
+	crt = uta_rt_clone( rt );
+	errors += fail_if_nil( crt, "cloned route table" );
+
+	ep = uta_get_ep( rt, "localhost:4561" );
+	errors += fail_if_nil( ep, "end point (fetch by name)" );
+	ep = uta_get_ep( rt, "bad_name:4560" );
+	errors += fail_not_nil( ep, "end point (fetch by name with bad name)" );
+
+	state = uta_epsock_byname( rt, "localhost:4561", &nn_sock );		// this should be found
+	errors += fail_if_equal( state, 0, "socket (by name)" );
+	//alt_value = uta_epsock_byname( rt, "localhost:4562" );			// we might do a memcmp on the two structs, but for now nothing
+	//errors += fail_if_equal( value, alt_value, "app1/app2 sockets" );
+
+	alt_value = -1;
+	for( i = 0; i < 10; i++ ) {										// round robin return value should be different each time
+		value = uta_epsock_rr( rt, 1, 0, &more, &nn_sock );			// msg type 1, group 1
+		errors += fail_if_equal( value, alt_value, "round robiin sockets with multiple end points" );
+		errors += fail_if_false( more, "more for mtype==1" );
+		alt_value = value;
+	}
+
+	more = -1;
+	for( i = 0; i < 10; i++ ) {							// this mtype has only one endpoint, so rr should be same each time
+		value = uta_epsock_rr( rt, 3, 0, NULL, &nn_sock );		// also test ability to deal properly with nil more pointer
+		if( i ) {
+			errors += fail_not_equal( value, alt_value, "round robin sockets with one endpoint" );
+			errors += fail_not_equal( more, -1, "more value changed in single group instance" );
+		}
+		alt_value = value;
+	}
+
+	value = uta_epsock_rr( rt, 9, 0, &more, &nn_sock );			// non-existant message type; should return false (0)
+	errors += fail_not_equal( value, 0, "socket for bad mtype was valid" );
+
+	uta_rt_clone( NULL );								// verify null parms don't crash things
+	uta_rt_drop( NULL );
+	uta_epsock_rr( NULL, 1, 0, &more, &nn_sock );		// drive null case for coverage
+	uta_add_rte( NULL, 99, 1 );
+
+	fprintf( stderr, "[INFO] test: adding end points with nil data; warnings expected\n" );
+	uta_add_ep( NULL, NULL, "foo", 1 );
+	uta_add_ep( rt, NULL, "foo", 1 );
+
+	buf = uta_fib( ".gitignore" );
+	errors += fail_if_nil( buf, "buffer from read file into buffer" );
+	if( buf ) {
+		free( buf );
+	}
+	buf = uta_fib( "no-file" );
+	errors += fail_if_nil( buf, "buffer from read file into buffer (no file)" );
+	if( buf ) {
+		free( buf );
+	}
+
+	uta_rt_drop( rt );
+
+	uta_rt_drop( crt );
+
+	if( (ctx = (uta_ctx_t *) malloc( sizeof( uta_ctx_t ) )) != NULL ) {
+		memset( ctx, 0, sizeof( *ctx ) );
+
+		if( (seed_fname = getenv( "RMR_SEED_RT" )) != NULL ) {
+			if( ! (fail_if_nil( rt, "pointer to rt for load test" )) ) {
+				errors++;
+				read_static_rt( ctx, 0 );
+				unsetenv( "RMR_SEED_RT" );			// unset to test the does not exist condition
+				read_static_rt( ctx, 0 );
+			} else {
+				fprintf( stderr, "<FAIL> cannot gen rt for load test\n" );
+			}
+		} else {
+			read_static_rt( ctx, 0 );		// not defined, just drive for that one case
+		}
+	}
+
+	uta_fib( "no-suhch-file" );			// drive some error checking for coverage
+
+/*
+	if( ctx ) {
+		if( ctx->rtg_addr ) {
+			free( ctx->rtg_addr );
+		}
+		free( ctx );
+	}
+*/
+
+	return !!errors;			// 1 or 0 regardless of count
+}
diff --git a/test/sr_nng_static_test.c b/test/sr_nng_static_test.c
new file mode 100644
index 0000000..69090a1
--- /dev/null
+++ b/test/sr_nng_static_test.c
@@ -0,0 +1,188 @@
+// : vi ts=4 sw=4 noet :
+/*
+==================================================================================
+        Copyright (c) 2019 Nokia 
+        Copyright (c) 2018-2019 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
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+	Mmemonic:	sr_nng_static_test.c
+	Abstract:	Test the send/receive funcitons. These are meant to be included at compile
+				time by the test driver.  
+
+	Author:		E. Scott Daniels
+	Date:		3 April 2019
+*/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "../src/common/include/rmr.h"
+#include "../src/common/include/rmr_agnostic.h"
+
+/*
+	Generate a simple route table (for all but direct route table testing).
+*/
+static void gen_rt( uta_ctx_t* ctx ) {
+	int		fd;
+	char* 	rt_stuff;		// strings for the route table
+
+	rt_stuff = 
+		"newrt|start\n"								// false start to drive detection 
+		"xxx|badentry to drive default case"
+		"newrt|start\n"
+        "rte|0|localhost:4560,localhost:4562\n"
+        "rte|1|localhost:4562;localhost:4561,localhost:4569\n"
+        "rte|2|localhost:4562\n"
+	    "rte|4|localhost:4561\n"
+		"rte|5|localhost:4563\n"
+        "rte|6|localhost:4562\n"
+		"newrt|end\n";
+
+	fd = open( "utesting.rt", O_WRONLY | O_CREAT, 0600 );
+	if( fd < 0 ) {
+		fprintf( stderr, "<BUGGERED> unable to open file for testing route table gen\n" );
+		return;
+	}
+
+	setenv( "RMR_SEED_RT", "utesting.rt", 1 );
+	write( fd, rt_stuff, strlen( rt_stuff ) );
+	close( fd );
+	read_static_rt( ctx, 0 );
+	unlink( "utesting.rt" );
+}
+
+
+/*
+	Drive the send and receive functions.  We also drive as much of the route
+	table collector as is possible without a real rtg process running somewhere.
+
+	Send and receive functions are indirectly exercised from the rmr_nng_static_test
+	module as it tests the user facing send/receive/call/rts functions. These tests
+	should exercise specific cases for the internal functions as they will not 
+	specifically be driven elsewhere.
+*/
+static int sr_nng_test() {
+	uta_ctx_t* ctx;				// context needed to test load static rt
+	uta_ctx_t*	real_ctx;	// real one to force odd situations for error testing
+	int errors = 0;			// number errors found
+	rmr_mbuf_t*	mbuf;		// mbuf to send/receive
+	rmr_mbuf_t*	mb2;		// error capturing msg buf
+	int		whid = -1;
+	int		last_whid;
+	int 	state;
+	nng_socket nn_dummy_sock;					// dummy needed to drive send
+	int		size;
+	int		i;
+
+	//ctx = rmr_init( "tcp:4360", 2048, 0 );				// do NOT call init -- that starts the rtc thread which isn't good here
+	ctx = (uta_ctx_t *) malloc( sizeof( uta_ctx_t ) );		// alloc the context manually
+	memset( ctx, 0, sizeof( uta_ctx_t ) );
+
+	ctx->mring = NULL;		//uta_mk_ring( 128 );
+	ctx->max_plen = RMR_MAX_RCV_BYTES + sizeof( uta_mhdr_t );
+	ctx->max_mlen = ctx->max_plen + sizeof( uta_mhdr_t );
+	ctx->my_name = strdup( "dummy-test" );
+	uta_lookup_rtg( ctx );
+
+	gen_rt( ctx );								// forces a static load with some known info since we don't start the rtc()
+
+	state = rmr_ready( NULL );
+	errors += fail_if_true( state, "reported ready when given a nil context" );
+	state = rmr_ready( ctx );
+	errors += fail_if_false( state, "reported not ready when it should be" );
+
+	mbuf = rcv_msg( ctx, NULL );
+	errors += fail_if_nil( mbuf, "no mbuf returned on receive test" );
+
+	mbuf->len = 10;
+	mbuf->mtype = 1;
+
+	mb2 = clone_msg( mbuf );
+	errors += fail_if_nil( mb2, "clone message returned nil pointer" );
+	errors += fail_not_equal( mbuf->flags, mb2->flags, "clone did not duplicate flags" );
+	errors += fail_not_equal( mbuf->alloc_len, mb2->alloc_len, "clone did not dup alloc-len" );
+	errors += fail_not_equal( mbuf->state, mb2->state, "clone did not dup state" );
+	rmr_free_msg( mb2 );	
+
+	mbuf = rmr_send_msg( NULL, mbuf );
+	errors += fail_if_nil( mbuf, "send with nil context but buffere didn't return buffer" );
+	if( mbuf ) {
+		errors += fail_not_equal( mbuf->state, RMR_ERR_BADARG, "send with buffer but nil context didn't return right state" );
+	} else {
+		mbuf = rmr_rcv_msg( ctx, NULL );
+	}
+
+	size = 2048 - sizeof( uta_mhdr_t );		// emulated nng receive allocates 2K payloads
+	state = rmr_payload_size( mbuf );
+	errors += fail_not_equal( state, size, "payload size didn't return expected value" );	// receive should always give 4k buffer
+
+	rmr_free_msg( mbuf );
+
+
+	state = xlate_nng_state( NNG_EAGAIN, 99 );
+	errors += fail_if( state == 99, "xlate_nng_state returned default for nng_eagain" );
+	errors += fail_if( errno != EAGAIN, "xlate_nng_state did not set errno to eagain for nng_eagain" );
+
+	state = xlate_nng_state( NNG_ETIMEDOUT, 99 );
+	errors += fail_if( state == 99, "xlate_nng_state returned default for nng_timeout" );
+	errors += fail_if( errno != EAGAIN, "xlate_nng_state did not set errno to eagain for nng_timeout" );
+
+	state = xlate_nng_state( NNG_ENOTSUP, 99 );
+	errors += fail_if( state != 99, "xlate_nng_state did not return  default for nng_notsup" );
+
+	state = xlate_nng_state( NNG_ENOTSUP, 99 );
+	errors += fail_if( state != 99, "xlate_nng_state did not return  default for nng_notsup" );
+	errors += fail_if( errno == 0, "xlate_nng_state did not set errno (1)" );
+
+	state = xlate_nng_state( NNG_EINVAL, 99 );
+	errors += fail_if( state != 99, "xlate_nng_state did not return  default for nng_inval" );
+	errors += fail_if( errno == 0, "xlate_nng_state did not set errno (2)" );
+
+	state = xlate_nng_state( NNG_ENOMEM, 99 );
+	errors += fail_if( state != 99, "xlate_nng_state did not return  default for nng_nomem" );
+	errors += fail_if( errno == 0, "xlate_nng_state did not set errno (3)" );
+
+	state = xlate_nng_state( NNG_ESTATE, 99 );
+	errors += fail_if( state != 99, "xlate_nng_state did not return  default for nng_state" );
+	errors += fail_if( errno == 0, "xlate_nng_state did not set errno (4)" );
+
+	state = xlate_nng_state( NNG_ECLOSED, 99 );
+	errors += fail_if( state != 99, "xlate_nng_state did not return  default for nng_closed" );
+	errors += fail_if( errno == 0, "xlate_nng_state did not set errno (5)" );
+
+	state = xlate_nng_state( 999, 99 );
+	errors += fail_if( state != 99, "xlate_nng_state did not return  default for unknown error" );
+	errors += fail_if( errno == 0, "xlate_nng_state did not set errno (6)" );
+
+	// ---- drive rtc in a 'static' (not pthreaded) mode -----
+	setenv( "ENV_VERBOSE_FILE", ".ut_rmr_verbose", 1 );			// allow for verbose code in rtc to be driven
+	i = open( ".rmr_verbose", O_CREAT, 0664 );
+	if( i >= 0 ) {
+		write( i, "0\n", 2 );
+		close( i );
+	}
+	ctx->shutdown = 1;			// should force rtc to quit on first pass
+	rtc( ctx );
+	
+
+	return !!errors;
+}
diff --git a/test/symtab_static_test.c b/test/symtab_static_test.c
new file mode 100644
index 0000000..181cd13
--- /dev/null
+++ b/test/symtab_static_test.c
@@ -0,0 +1,142 @@
+/*
+==================================================================================
+        Copyright (c) 2019 Nokia 
+        Copyright (c) 2018-2019 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
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+
+/*
+	Mnemonic:	symtab_static_test.c
+	Abstract:	This is the static function that should be included by 
+				any test that wants to test the symbol table. It must
+				be included in the compile, and not built to object.
+
+	Date:		1 April 2019
+	Author: 	E. Scott Daniels
+*/
+
+#include "../src/common/include/rmr_symtab.h"
+//  -- parent must include if needed #include "../src/common/src/symtab.c"
+
+#include "test_support.c"
+
+#ifndef GOOD 
+#define GOOD 0
+#define BAD 1
+#endif
+
+int symtab_state = GOOD;							// overall pass/fail state 0==fail
+int symtab_counter = 0;								// global counter for for-each tests
+
+static void st_fetch( void* st, char* key, int class, int expected ) {
+	char* val;
+
+	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 ) {
+			symtab_state = BAD;
+		}
+		
+	} else {
+		fprintf( stderr, "<%s> string key st_fetch return nil\n", expected ? "FAIL" : "OK" );
+		if( expected ) {
+			symtab_state = BAD;
+		}
+	}
+}
+
+static void st_nfetch( void* st, int key, int expected ) {
+	char* val;
+
+	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 )  {
+			symtab_state = BAD;
+		}
+	} else {
+		fprintf( stderr, "<%s> get return nil for key=%d\n", expected ? "FAIL" : "OK", key );
+		if( expected )  {
+			symtab_state = BAD;
+		}
+	}
+}
+
+
+/*
+	Driven by foreach class -- just incr the counter.
+*/
+static void each_counter( void* a, void* b, const char* c, void* d, void* e ) {
+	symtab_counter++;
+}
+
+static int symtab_test( ) {
+    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
+	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
+	errors += 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)
+	errors += fail_if_true( s, "insert foo existed" );
+
+	st_fetch( st, foo, class, 1 );
+	st_fetch( st, goo, class, 0 );					// st_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
+		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, 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
+	errors += fail_if_false( s, "numeric add of key 1234 should not have existed" );
+	s = rmr_sym_map( st, 2345, bar );
+	errors += fail_if_true( s, "numeric add of key 2345 should have existed" );
+
+	symtab_counter = 0;
+	rmr_sym_foreach_class( st, 0, each_counter, NULL );
+	errors += fail_if_false( symtab_counter, "expected counter after foreach to be non-zero" );
+
+	st_nfetch( st, 1234, 1 );
+	st_nfetch( st, 2345, 1 );
+
+    rmr_sym_del( st, foo, 0 );		// drive for coverage
+    rmr_sym_stats( st, 0 );
+
+	rmr_sym_free( NULL );			// ensure it doesn't barf when given a nil pointer
+	rmr_sym_free( st );
+
+    return !!( errors + symtab_state );
+}
+
diff --git a/test/test_nng_em.c b/test/test_nng_em.c
new file mode 100644
index 0000000..76fe72b
--- /dev/null
+++ b/test/test_nng_em.c
@@ -0,0 +1,393 @@
+/*
+==================================================================================
+	Copyright (c) 2019 Nokia 
+	Copyright (c) 2018-2019 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
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+	Mnemonic:	test_nng_em.c
+	Abstract:	A nano/NNG message emulator for testing without needing to 
+				actually have nanomsg, nng, or external processes.
+				We also emulate the epoll_wait() function for controlled
+				poll related testing.
+
+				This module must be directly included to be used.
+	Date:		11 February 2019
+	Author:		E. Scott Daniels
+*/
+
+// ---------------------- emulated nng functions ---------------------------
+
+
+#ifndef _em_nn
+#define _em_nn
+
+static int em_send_failures = 0;	// test programme can set this to emulate eagain send failures
+
+// ----------- epoll emulation ---------------------------------------------
+
+// CAUTION: sys/epoll.h must be included before this define and function will properly compile. 
+#define epoll_wait em_wait
+/*
+	Every other call returns 1 ready; alternate calls return 0 ready. 
+	Mostly for testing the timeout receive call. First call should return 
+	something ready and the second should return nothing ready so we can
+	drive both cases. 
+*/
+static int em_wait( int fd, void* events, int n, int to ) {
+	static int ready = 0;
+
+	ready = !ready;
+	return ready;
+}
+
+
+
+//--------------------------------------------------------------------------
+#ifdef EMULATE_NNG
+struct nn_msghdr {
+	int boo;
+};
+
+static int return_value = 0;
+
+/*
+	Test app can call this to have all emulated functions return failure instead
+	of success.
+*/
+static void en_set_retur( int rv ) {
+	return_value = rv;
+}
+
+
+
+static int em_nng_foo() {
+	fprintf( stderr, "emulated functions in play" );
+}                              
+
+
+/*
+	Simulated v1 message for receive to return. This needs to match the RMr header
+	so that we can fill in length, type and xaction id things.
+#define MSG_VER 1
+struct em_msg {
+	int32_t	mtype;						// message type  ("long" network integer)
+	int32_t	plen;						// payload length
+	int32_t rmr_ver;					// our internal message version number
+	unsigned char xid[32];				// space for user transaction id or somesuch
+	unsigned char sid[32];				// sender ID for return to sender needs
+	unsigned char src[16];				// name of the sender (source)
+	unsigned char meid[32];				// managed element id.
+	struct timespec	ts;					// timestamp ???
+};
+*/
+
+/*
+	v2 message; should be able to use it for everything that is set up here as 
+	we don't add a payload even if setting a v1 type.
+*/
+#define ALT_MSG_VER 1	// alternate every so often
+#define MSG_VER 2		// default version to insert
+struct em_msg {
+	int32_t	mtype;						// message type  ("long" network integer)
+	int32_t	plen;						// payload length
+	int32_t rmr_ver;					// our internal message version number
+	unsigned char xid[32];				// space for user transaction id or somesuch
+	unsigned char sid[32];				// sender ID for return to sender needs
+	unsigned char src[64];				// name of the sender (source)
+	unsigned char meid[32];				// managed element id.
+	struct timespec	ts;					// timestamp ???
+
+                                        // V2 extension
+    int32_t flags;                      // HFL_* constants
+    int32_t len0;                       // length of the RMr header data
+    int32_t len1;                       // length of the tracing data
+    int32_t len2;                       // length of data 1 (d1)
+    int32_t len3;                       // length of data 2 (d2)
+
+};
+
+/*
+	Receive message must allocate a new buffer and return the pointer into *m.
+	Every 9 messages or so we'll simulate an old version message
+*/
+static int em_nng_recvmsg( nng_socket s, nng_msg ** m, int i ) {
+	void* b;
+	struct em_msg* msg;
+	static int count = 0;			// we'll simulate a message going in by dropping an rmr-ish msg with transaction id only
+	int trace_size = 0;
+
+	//sleep( 1 );
+
+	b = (void *) malloc( 2048 );
+	if( m != NULL ) {
+		memset( b, 0, 2048 );
+		*m = (nng_msg *) b;
+		msg = (struct em_msg *) b;
+		if( count % 10  == 9 ) {
+			//msg->rmr_ver = htonl( MSG_VER );
+			msg->rmr_ver = ALT_MSG_VER;		// emulate the bug in RMr v1
+		} else {
+			msg->rmr_ver = htonl( MSG_VER );
+		}
+		msg->mtype = htonl( 1 );
+		msg->plen = htonl( 129 );
+		msg->len0 = htonl( sizeof( struct em_msg ) );
+		msg->len1 = htonl( trace_size );
+		snprintf( msg->xid, 32, "%015d", count++ );		// simple transaction id so we can test receive specific and ring stuff
+		snprintf( msg->src, 16, "localhost:4562" );		// set src id (unrealistic) so that rts() can be tested
+	}
+
+	//fprintf( stderr, ">>> simulated received message: %s\n", msg->xid );
+	return return_value;
+}
+
+static void* em_msg_body( nng_msg* msg ) {
+	return (void *) msg;								// we don't manage a real msg, so body is just the buffer we allocated
+}
+
+static size_t em_msg_len( const nng_msg* msg ) {
+	if( msg ) {
+		return  2048;
+	}
+
+	return 0;
+}
+
+
+static int em_nng_pull_open(nng_socket * s ) {
+	return return_value;
+}
+static int em_nng_pull0_open(nng_socket * s ) {
+	return return_value;
+}
+static int em_nng_listen(nng_socket s, const char * c, nng_listener * l, int i ) {
+	return return_value;
+}
+static int em_nng_close(nng_socket s ) {
+	return return_value;
+}
+static int em_nng_push0_open(nng_socket * s ) {
+	return return_value;
+}
+static int em_nng_dial(nng_socket s, const char * c, nng_dialer * d, int i ) {
+	//fprintf( stderr, "<info> === simulated dialing: %s\n", c );
+	return return_value;
+}
+static int em_nng_setopt(nng_socket s, const char * c, const void * p, size_t t ) {
+	return return_value;
+}
+static int em_nng_sub_open(nng_socket * s ) {
+	return return_value;
+}
+static int em_nng_sub0_open(nng_socket * s ) {
+	return return_value;
+}
+static int em_nng_recv(nng_socket s, void * v, size_t * t, int i ) {
+	return return_value;
+}
+static int em_nng_send( nng_socket s, void* m, int l, int f ) {
+	return return_value;
+}
+
+/*
+	Emulate sending a message. If the global em_send_failures is set,
+	then every so often we fail with an EAGAIN to drive that part 
+	of the code in RMr.
+*/
+static int em_sendmsg( nng_socket s, nng_msg* m, int i ) {
+	static int count = 0;
+
+	if( em_send_failures && (count++ % 15 == 14) ) {
+		//fprintf( stderr, ">>>> failing send\n\n" );
+		return NNG_EAGAIN;
+	}
+
+	return return_value;
+}
+
+static void* em_nng_alloc( size_t len ) {
+	return malloc( len );
+}
+
+static int em_nng_msg_alloc( nng_msg** mp, size_t l ) {
+	void*	p;
+
+	if( !mp || return_value != 0  ) {
+		return -1;
+	}
+
+	p = (void *) malloc( sizeof( char ) * l );
+	*mp = (nng_msg *) p;
+
+	return return_value;
+}
+
+/*
+	We just free the buffer here as it was a simple malloc.
+*/
+static void em_nng_free( void* p, size_t l ) {
+	if( p ) {
+		//fprintf( stderr, ">>>>> not freed: %p\n", p );
+		free( p );
+	}
+}
+static void em_nng_msg_free( void* p ) {
+	if( p ) {
+		//fprintf( stderr, ">>>>> not freed: %p\n", p );
+		free( p );
+	}
+}
+
+static int em_dialer_create( void* d, nng_socket s, char* stuff ) {
+	//fprintf( stderr, ">>>> emulated dialer create\n\n" );
+	return 0;
+}
+
+static int em_dialer_start( nng_dialer d, int i ) {
+	//fprintf( stderr, ">>>> emulated dialer start\n\n" );
+	return return_value;
+}
+
+
+static int em_dialer_setopt_ms( nng_dialer dialer, void* option, int ms ) {
+	return return_value;
+}
+
+static int em_nng_getopt_int( nng_socket s, void* con, int* target ) {
+	if( target ) {
+		*target = 0;
+	}
+	return return_value;
+}
+
+
+
+// nng redefines some of these to point directly to various 'versions' of the function (ugg, function versions, really?)
+#undef nng_recvmsg 
+#undef nng_free 
+#undef nng_pull_open 
+#undef nng_pull0_open 
+#undef nng_listen 
+#undef nng_close 
+#undef nng_getopt_int 
+#undef nng_push0_open 
+#undef nng_dial 
+#undef nng_setopt 
+#undef nng_sub_open 
+#undef nng_sub0_open 
+#undef nng_recv 
+#undef nng_alloc 
+
+#define nng_msg_alloc em_nng_msg_alloc
+#define nng_recvmsg em_nng_recvmsg
+#define nng_free em_nng_free
+#define nng_free em_nng_free
+#define nng_msg_free em_nng_msg_free
+#define nng_pull_open em_nng_pull_open
+#define nng_pull0_open em_nng_pull0_open
+#define nng_listen em_nng_listen
+#define nng_close em_nng_close
+#define nng_getopt_int em_nng_getopt_int
+#define nng_push0_open em_nng_push0_open
+#define nng_dial em_nng_dial
+#define nng_setopt em_nng_setopt
+#define nng_sub_open em_nng_sub_open
+#define nng_sub0_open em_nng_sub0_open
+#define nng_recv em_nng_recv
+#define nng_send em_nng_send
+#define nng_sendmsg em_sendmsg
+#define nng_alloc em_nng_alloc
+#define nng_free em_nng_free
+#define nng_dialer_setopt_ms em_dialer_setopt_ms
+#define nng_dialer_start em_dialer_start
+#define nng_dialer_create em_dialer_create
+#define nng_msg_body em_msg_body
+#define nng_msg_len em_msg_len
+
+
+#else
+
+
+// ----------------------- emulated nano functions --------------------------
+struct em_nn_msghdr {
+	int dummy;
+};
+
+static int em_nn_socket (int domain, int protocol ) {
+	static int s = 1;
+
+	return ++s;
+}
+
+static int em_nn_close (int s ) {
+	return 1;
+}
+
+static int em_nn_setsockopt (int s, int level, int option, const void *optval, size_t optvallen ) {
+	return 1;
+}
+
+static int em_nn_getsockopt (int s, int level, int option, void *optval, size_t *optvallen ) {
+	return 1;
+}
+
+static int em_nn_bind (int s, const char *addr ) {
+fprintf( stderr, ">>> ===== emulated bind called ====\n" );
+	return 1;
+}
+
+static int em_nn_connect (int s, const char *addr ) {
+	return 1;
+}
+
+static int em_nn_shutdown (int s, int how ) {
+	return 1;
+}
+
+static int em_nn_send (int s, const void *buf, size_t len, int flags ) {
+	return 1;
+}
+
+static int em_nn_recv (int s, void *buf, size_t len, int flags ) {
+	return 1;
+}
+
+static int em_sendmsg (int s, const struct em_nn_msghdr *msghdr, int flags ) {
+	return 1;
+}
+
+static int em_nn_recvmsg (int s, struct nn_msghdr *msghdr, int flags ) {
+	return 1;
+}
+
+// nanomsg 
+#define nn_socket  em_nn_socket
+#define nn_close  em_nn_close
+#define nn_setsockopt  em_nn_setsockopt
+#define nn_getsockopt  em_nn_getsockopt
+#define nn_bind  em_nn_bind
+#define nn_connect  em_nn_connect
+#define nn_shutdown  em_nn_shutdown
+#define nn_send  em_nn_send
+#define nn_recv  em_nn_recv
+#define nn_sendmsg  em_nn_sendmsg
+#define nn_recvmsg  em_nn_recvmsg
+
+#endif 
+
+
+#endif
diff --git a/test/test_support.c b/test/test_support.c
index d9a5f47..4ed976c 100644
--- a/test/test_support.c
+++ b/test/test_support.c
@@ -27,10 +27,15 @@
 	Date:		6 January 2019
 */
 
+#ifndef _test_support_c
+#define _test_support_c
+
 #include <signal.h>
 #include <string.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
 
 #ifndef BAD
 #define BAD 1			// these are exit codes unless user overrides
@@ -81,21 +86,21 @@
 
 static int fail_if_nil( void* p, char* what ) {
 	if( !p ) {
-		fprintf( stderr, "[FAIL] pointer to '%s' was nil\n", what );
+		fprintf( stderr, "<FAIL> %s: pointer was nil\n", what );
 	}
 	return p ? GOOD : BAD;
 }
 
 static int fail_not_nil( void* p, char* what ) {
 	if( p ) {
-		fprintf( stderr, "[FAIL] pointer to '%s' was not nil\n", what );
+		fprintf( stderr, "<FAIL> %s: pointer was not nil\n", what );
 	}
 	return !p ? GOOD : BAD;
 }
 
 static int fail_if_false( int bv, char* what ) {
 	if( !bv ) {
-		fprintf( stderr, "[FAIL] boolean was false (%d) %s\n", bv, what );
+		fprintf( stderr, "<FAIL> %s: expected true, boolean test was false (%d)\n", what, bv );
 	}
 
 	return bv ? GOOD : BAD;
@@ -103,7 +108,7 @@
 
 static int fail_if_true( int bv, char* what ) {
 	if( bv ) {
-		fprintf( stderr, "[FAIL] boolean was true (%d) %s\n", bv, what );
+		fprintf( stderr, "<FAIL> %s: expected false, boolean test was true (%d)\n", what, bv );
 	}
 	return bv ? BAD : GOOD;
 }
@@ -114,21 +119,23 @@
 static int fail_if( int bv, char* what ) {
 
 	if( bv ) {
-		fprintf( stderr, "[FAIL] boolean was true (%d) %s\n", bv, what );
+		fprintf( stderr, "<FAIL> %s: expected false, boolean test was true (%d)\n", what, bv );
 	}
 	return bv ? BAD : GOOD;
 }
 
 static int fail_not_equal( int a, int b, char* what ) {
 	if( a != b ) {
-		fprintf( stderr, "[FAIL] %s values were not equal a=%d b=%d\n", what, a, b );
+		fprintf( stderr, "<FAIL> %s: values were not equal a=%d b=%d\n", what, a, b );
 	}
 	return a == b ? GOOD : BAD;			// user may override good/bad so do NOT return a==b directly!
 }
 
 static int fail_if_equal( int a, int b, char* what ) {
 	if( a == b ) {
-		fprintf( stderr, "[FAIL] %s values were equal a=%d b=%d\n", what, a, b );
+		fprintf( stderr, "<FAIL> %s values were equal a=%d b=%d\n", what, a, b );
 	}
 	return a != b ? GOOD : BAD;			// user may override good/bad so do NOT return a==b directly!
 }
+
+#endif
diff --git a/test/tools_static_test.c b/test/tools_static_test.c
new file mode 100644
index 0000000..66ddeb6
--- /dev/null
+++ b/test/tools_static_test.c
@@ -0,0 +1,154 @@
+// : vi ts=4 sw=4 noet :
+/*
+==================================================================================
+        Copyright (c) 2019 Nokia 
+        Copyright (c) 2018-2019 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
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+
+/*
+	Mnemonic:	tools_static_test.c
+	Abstract:	Unit tests for the RMr tools module. This file is a static include
+				that is pulle in at compile time by the test driver.  The driver is
+				expected to include necessary rmr*.h and test_support files before
+				including this file.  In addition, a context struct, or dummy, must
+				be provided based on the type of testing being done.
+
+	Author:		E. Scott Daniels
+	Date:		3 April 2019
+*/
+
+
+static int tools_test( ) {
+	int i;
+	int j;
+	int errors = 0;
+	char* tokens[127];
+	char* buf = "2,Fred,Wilma,Barney,Betty,Dino,Pebbles,Bambam,Mr. Slate,Gazoo";
+	char*	dbuf;				// duplicated buf since C marks a const string is unumtable
+	char*	hname;
+	uta_ctx_t ctx;				// context for uta_lookup test
+	void*	if_list;
+
+	
+	// ------------------ tokenise tests -----------------------------------------------------------
+	dbuf = strdup( buf );
+	i = uta_tokenise( dbuf, tokens, 127, ',' );
+	errors += fail_not_equal( i, 10, "unexpected number of tokens returned (comma sep)" );
+	for( j = 0; j < i; j++ ) {
+		//fprintf( stderr, ">>>> [%d] (%s)\n", j, tokens[j] );
+		errors += fail_if_nil( tokens[j], "token from buffer" );
+	}
+	errors += fail_not_equal( strcmp( tokens[4], "Betty" ), 0, "4th token wasn't 'Betty'" );
+
+	free( dbuf );
+	dbuf = strdup( buf );
+	i = uta_tokenise( dbuf, tokens, 127, '|' );
+	errors += fail_not_equal( i, 1, "unexpected number of tokens returned (bar sep)" );
+	free( dbuf );
+
+	// ------------ has str tests -----------------------------------------------------------------
+	j = uta_has_str( buf, "Mr. Slate", ',', 1 );			// should fail (-1) because user should use strcmp in this situation
+	errors += fail_if_true( j >= 0, "test to ensure has str rejects small max" );
+
+	j = uta_has_str( buf, "Mr. Slate", ',', 27 );
+	errors += fail_if_true( j < 0, "has string did not find Mr. Slate" );
+
+	j = uta_has_str( buf, "Mrs. Slate", ',', 27 );
+	errors += fail_if_true( j >= 0, "has string not found Mrs. Slate" );
+	
+	// ------------ host name 2 ip tests ---------------------------------------------------------
+	hname = uta_h2ip( "192.168.1.2" );
+	errors += fail_not_equal( strcmp( hname, "192.168.1.2" ), 0, "h2ip did not return IP address when given address" );
+	errors += fail_if_nil( hname, "h2ip did not return a pointer" );
+
+	hname = uta_h2ip( "yahoo.com" );
+	errors += fail_if_nil( hname, "h2ip did not return a pointer" );
+
+	hname = uta_h2ip( "yahoo.com:1234" );							// should ignore the port
+	errors += fail_if_nil( hname, "h2ip did not return a pointer" );
+
+	// ------------ rtg lookup test -------------------------------------------------------------
+	ctx.rtg_port = 0;
+	ctx.rtg_addr = NULL;
+       
+	i = uta_lookup_rtg( NULL );						// ensure it handles a nil context
+	errors += fail_if_true( i, "rtg lookup returned that it found something when not expected to (nil context)" );
+
+	setenv( "RMR_RTG_SVC", "localhost:1234", 1);
+	i = uta_lookup_rtg( &ctx );
+	errors += fail_if_false( i, "rtg lookup returned that it did not find something when expected to" );
+	errors += fail_if_nil( ctx.rtg_addr, "rtg lookup did not return a pointer (with port)" );
+	errors += fail_not_equal( ctx.rtg_port, 1234, "rtg lookup did not capture the port" );
+
+	setenv( "RMR_RTG_SVC", "localhost", 1);			// test ability to generate default port
+	uta_lookup_rtg( &ctx );
+	errors += fail_if_nil( ctx.rtg_addr, "rtg lookup did not return a pointer (no port)" );
+	errors += fail_not_equal( ctx.rtg_port, 5656, "rtg lookup did not return default port" );
+
+	unsetenv( "RMR_RTG_SVC" );						// this should fail as the default name (rtg) will be unknown during testing
+	i = uta_lookup_rtg( &ctx );
+	errors += fail_if_true( i, "rtg lookup returned that it found something when not expected to" );
+
+/*
+//==== moved out of generic tools ==========
+	// -------------- test link2 stuff ----------------------------------------------------------
+	i = uta_link2( "bad" );					// should fail
+	errors += fail_if_true( i >= 0, "uta_link2 didn't fail when given bad address" );
+
+	i = uta_link2( "nohost:-1234" );
+	errors += fail_if_true( i >= 0, "uta_link2 did not failed when given a bad (negative) port " );
+
+	i = uta_link2( "nohost:1234" );					// nn should go off and set things up, but it will never successd, but uta_ call should
+	errors += fail_if_true( i < 0, "uta_link2 failed when not expected to" );
+*/
+
+	// ------------ my ip stuff -----------------------------------------------------------------
+
+	if_list = mk_ip_list( "1235" );
+	errors += fail_if_nil( if_list, "mk_ip_list returned nil pointer" );
+
+	i = has_myip( NULL, NULL, ',', 128 );		// should be false if pointers are nil
+	errors += fail_if_true( i, "has_myip returned true when given nil buffer" );
+
+	i = has_myip( "buffer contents not valid", NULL, ',', 128 );		// should be false if pointers are nil
+	errors += fail_if_true( i, "has_myip returned true when given nil list" );
+
+	i = has_myip( "buffer contents not valid", NULL, ',', 1 );			// should be false if max < 2
+	errors += fail_if_true( i, "has_myip returned true when given small max value" );
+
+	i = has_myip( "buffer.contents.not.valid", if_list, ',', 128 );		// should be false as there is nothing valid in the list
+	errors += fail_if_true( i, "has_myip returned true when given a buffer with no valid info" );
+
+
+	setenv( "RMR_BIND_IF", "192.168.4.30", 1 );			// drive the case where we have a hard set interface; and set known interface in list
+	if_list = mk_ip_list( "1235" );
+	errors += fail_if_nil( if_list, "mk_ip_list with env set returned nil pointer" );
+
+	i = has_myip( "192.168.1.2:1235,192.168.4.30:1235,192.168.2.19:4567", if_list, ',', 128 );		// should find our ip in middle
+	errors += fail_if_false( i, "has_myip did not find IP in middle of list" );
+
+	i = has_myip( "192.168.4.30:1235,192.168.2.19:4567,192.168.2.19:2222", if_list, ',', 128 );		// should find our ip at head
+	errors += fail_if_false( i, "has_myip did not find IP at head of list" );
+
+	i = has_myip( "192.168.23.45:4444,192.168.1.2:1235,192.168.4.30:1235", if_list, ',', 128 );		// should find our ip at end
+	errors += fail_if_false( i, "has_myip did not find IP at tail of list" );
+
+	i = has_myip( "192.168.4.30:1235", if_list, ',', 128 );											// should find our ip when only in list
+	errors += fail_if_false( i, "has_myip did not find IP when only one in list" );
+
+	return !!errors;			// 1 or 0 regardless of count
+}
diff --git a/test/tools_test.c b/test/tools_test.c
index 786d2e6..a29c0e2 100644
--- a/test/tools_test.c
+++ b/test/tools_test.c
@@ -36,14 +36,6 @@
 #include <pthread.h>
 #include <ctype.h>
 
-/*
-#include <nanomsg/nn.h>
-#include <nanomsg/tcp.h>
-#include <nanomsg/pair.h>
-#include <nanomsg/pipeline.h>
-#include <nanomsg/pubsub.h>
-*/
-
 #include "../src/common/include/rmr.h"
 #include "../src/common/include/rmr_agnostic.h"
 #include "test_support.c"		// our private library of test tools
@@ -148,18 +140,6 @@
 	i = uta_lookup_rtg( &ctx );
 	errors += fail_if_true( i, "rtg lookup returned that it found something when not expected to" );
 
-/*
-//==== moved out of generic tools ==========
-	// -------------- test link2 stuff ----------------------------------------------------------
-	i = uta_link2( "bad" );					// should fail
-	errors += fail_if_true( i >= 0, "uta_link2 didn't fail when given bad address" );
-
-	i = uta_link2( "nohost:-1234" );
-	errors += fail_if_true( i >= 0, "uta_link2 did not failed when given a bad (negative) port " );
-
-	i = uta_link2( "nohost:1234" );					// nn should go off and set things up, but it will never successd, but uta_ call should
-	errors += fail_if_true( i < 0, "uta_link2 failed when not expected to" );
-*/
 
 	// ------------ my ip stuff -----------------------------------------------------------------
 
diff --git a/test/unit_test.ksh b/test/unit_test.ksh
index fb5fa2a..745cf07 100755
--- a/test/unit_test.ksh
+++ b/test/unit_test.ksh
@@ -51,17 +51,26 @@
 #				but only the result of the discount test is taken into 
 #				consideration with regard to overall success.
 #
+#				Overall Pass/Fail
+#				By default the overall state is based only on the success
+#				or failure of the unit tests and NOT on the perceived 
+#				state of coverage.  If the -s (strict) option is given, then
+#				overall state will be failure if code coverage expectations
+#				are not met.
+#
 #	Date:		16 January 2018
 #	Author:		E. Scott Daniels
 # -------------------------------------------------------------------------
 
 function usage {
-	echo "usage: $0 [-G|-M|-C custom-command-string] [-c cov-target]  [-f] [-v]  [files]"
+	echo "usage: $0 [-G|-M|-C custom-command-string] [-c cov-target]  [-f] [-F] [-v]  [files]"
 	echo "  if -C is used to provide a custom build command then it must "
 	echo "  contain a %s which will be replaced with the unit test file name."
 	echo '  e.g.:  -C "mk -a %s"'
 	echo "  -c allows user to set the target coverage for a module to pass; default is 80"
 	echo "  -f forces a discount check (normally done only if coverage < target)"
+	echo "  -F show only failures at the function level"
+	echo "  -s strict mode; code coverage must also pass to result in a good exit code"
 	echo "  -v will write additional information to the tty and save the disccounted file if discount run or -f given"
 }
 
@@ -82,7 +91,7 @@
 		}
 	' | while read f
 	do
-		iflist+="$f "	
+		iflist="${iflist}$f "	
 	done
 }
 
@@ -90,7 +99,7 @@
 #	Parse the .gcov file and discount any unexecuted lines which are in if() 
 #	blocks that are testing the result of alloc/malloc calls, or testing for
 #	nil pointers.  The feeling is that these might not be possible to drive
-#	and shoudn't contribute to coverage deficencies.
+#	and shoudn't contribute to coverage deficiencies.
 #
 #	In verbose mode, the .gcov file is written to stdout and any unexecuted
 #	line which is discounted is marked with ===== replacing the ##### marking
@@ -114,9 +123,11 @@
 	fi
 
 	awk -v module_cov_target=$mct \
+		-v cfail=${cfail:-WARN} \
+		-v show_all=$show_all \
 		-v full_name="${1}"  \
 		-v module="${f%.*}"  \
-		-v chatty=$verbose \
+		-v chatty=1 \
 	'
 	function spit_line( ) {
 		if( chatty ) {
@@ -201,13 +212,15 @@
 		net = unexec - discount
 		orig_cov = ((nexec-unexec)/nexec)*100		# original coverage
 		adj_cov = ((nexec-net)/nexec)*100			# coverage after discount
-		pass_fail = adj_cov < module_cov_target ? "FAIL" : "PASS"
+		pass_fail = adj_cov < module_cov_target ? cfail : "PASS"
 		rc = adj_cov < module_cov_target ? 1 : 0
-		if( chatty ) {
-			printf( "[%s] %s executable=%d unexecuted=%d discounted=%d net_unex=%d  cov=%d% ==> %d%%%  target=%d%%\n", 
-				pass_fail, full_name ? full_name : module, nexec, unexec, discount, net, orig_cov, adj_cov, module_cov_target )
-		} else {
-			printf( "[%s] %d%% (%d%%) %s\n", pass_fail, adj_cov, orig_cov, full_name ? full_name : module )
+		if( pass_fail == cfail || show_all ) {
+			if( chatty ) {
+				printf( "[%s] %s executable=%d unexecuted=%d discounted=%d net_unex=%d  cov=%d% ==> %d%%%  target=%d%%\n", 
+					pass_fail, full_name ? full_name : module, nexec, unexec, discount, net, orig_cov, adj_cov, module_cov_target )
+			} else {
+				printf( "[%s] %d%% (%d%%) %s\n", pass_fail, adj_cov, orig_cov, full_name ? full_name : module )
+			}
 		}
 
 		exit( rc )
@@ -232,12 +245,27 @@
 
 # ------------------------------------------------------------------------
 
+# we assume that the project has been built in the ../[.]build directory
+if [[ -d ../build/lib ]]
+then
+	export LD_LIBRARY_PATH=../build/lib
+else
+	if [[ -d ../.build/lib ]]
+	then
+		export LD_LIBRARY_PATH=../.build/lib
+	else
+		echo "[WARN] cannot find ../[.]build/lib; things might not work"
+		echo ""
+	fi
+fi
+
 export C_INCLUDE_PATH="../src/common/include"
 
 module_cov_target=80
 builder="make -B %s"		# default to plain ole make
 verbose=0
-trigger_discount_str="FAIL"
+show_all=1					# show all things -F sets to show failures only
+strict=0					# -s (strict) will set; when off, coverage state ignored in final pass/fail
 
 while [[ $1 == "-"* ]]
 do
@@ -248,9 +276,12 @@
 
 		-c)	module_cov_target=$2; shift;;
 		-f)	force_discounting=1; 
-			trigger_discount_str="FAIL|PASS"		# check all outcomes for each module
+			trigger_discount_str="WARN|FAIL|PASS"		# check all outcomes for each module
 			;;
 
+		-F)	show_all=0;;
+
+		-s)	strict=1;;					# coverage counts toward pass/fail state
 		-v)	(( verbose++ ));;
 
 		-h) 	usage; exit 0;;
@@ -266,17 +297,35 @@
 	shift
 done
 
+
+if (( strict )) 		# if in strict mode, coverage shortcomings are failures
+then
+	cfail="FAIL"
+else
+	cfail="WARN"
+fi
+if [[ -z $trigger_discount_str ]]
+then
+	trigger_discount_str="$cfail"
+fi
+
+
 if [[ -z $1 ]]
 then
 	flist=""
 	for tfile in *_test.c
 	do
-		flist+="$tfile "
+		if [[ $tfile != *"static_test.c" ]]
+		then
+			flist="${flist}$tfile "
+		fi
 	done
 else
 	flist="$@"
 fi
 
+
+ut_errors=0			# unit test errors (not coverage errors)
 errors=0
 for tfile in $flist
 do
@@ -293,12 +342,18 @@
 	iflist="main sig_clean_exit "		# ignore external functions from our tools
 	add_ignored_func $tfile				# ignore all static functions in our test driver
 	add_ignored_func test_support.c		# ignore all static functions in our test tools
+	add_ignored_func test_nng_em.c		# the nng/nano emulated things
+	for f in *_static_test.c			# all static modules here
+	do
+		add_ignored_func $f
+	done
 	
 	if ! ${tfile%.c} >/tmp/PID$$.log 2>&1
 	then
 		echo "[FAIL] unit test failed for: $tfile"
 		cat /tmp/PID$$.log
-		continue
+		(( ut_errors++ ))				# cause failure even if not in strict mode
+		continue						# skip coverage tests for this
 	fi
 
 	(
@@ -306,11 +361,14 @@
 		sed '/^#/ d; /^$/ d; s/^/TARGET: /' ./.targets
 		gcov -f ${tfile%.c} | sed "s/'//g" 
 	) | awk \
+		-v cfail=$cfail \
+		-v show_all=$show_all \
 		-v ignore_list="$iflist" \
 		-v module_cov_target=$module_cov_target \
 		-v chatty=$verbose \
 		'
 		BEGIN {
+			announce_target = 1;
 			nignore = split( ignore_list, ignore, " " )
 			for( i = 1; i <= nignore; i++ ) {
 				imap[ignore[i]] = 1
@@ -359,30 +417,48 @@
 			pct = a[2]+0
 
 			if( file ) {
+				if( announce_target ) {				# announce default once at start
+					announce_target = 0;
+					printf( "\n[INFO] default target coverage for modules is %d%%\n", module_cov_target )
+				}
+
 				if( target[fname] ) {
 					mct = target[fname] 
+					announce_target = 1;
 				} else {
 					mct = module_cov_target
 				}
-				if( chatty ) {
+
+				if( announce_target ) {					# annoucne for module if different from default
 					printf( "[INFO] target coverage for %s is %d%%\n", fname, mct )
 				}
+
 				if( pct < mct ) {
-					printf( "[FAIL] %3d%% %s\n\n", pct, fname )	# CAUTION: write only 3 things  here
+					printf( "[%s] %3d%% %s\n", cfail, pct, fname )	# CAUTION: write only 3 things  here
 					exit_code = 1
 				} else {
-					printf( "[PASS] %3d%% %s\n\n", pct, fname )
+					printf( "[PASS] %3d%% %s\n", pct, fname )
 				}
+
+				announce_target = 0;
 			} else {
-				if( pct < 80 ) {
+				if( pct < 70 ) {
 					printf( "[LOW]  %3d%% %s\n", pct, fname )
 				} else {
-					printf( "[OK]   %3d%% %s\n", pct, fname )
+					if( pct < 80 ) {
+						printf( "[MARG] %3d%% %s\n", pct, fname )
+					} else {
+						if( show_all ) {
+							printf( "[OK]   %3d%% %s\n", pct, fname )
+						}
+					}
 				}
 			}
+
 		}
 
 		END {
+			printf( "\n" );
 			exit( exit_code )
 		}
 	' >/tmp/PID$$.log					# capture output to run discount on failures
@@ -390,27 +466,52 @@
 	cat /tmp/PID$$.log
 	if (( rc  || force_discounting )) 	# didn't pass, or forcing, see if discounting helps
 	then
+		show_all=1
+		if (( ! verbose ))
+		then
+			echo "[INFO] checking to see if discounting improves coverage for failures listed above"
+		fi
+
 		egrep "$trigger_discount_str"  /tmp/PID$$.log | while read state junk  name
 		do
-			echo "[INFO] checking to see if discounting improves coverage for $name"
 			if ! discount_an_checks $name.gcov >/tmp/PID$$.disc
 			then
 				(( errors++ ))
 			fi
+
 			tail -1 /tmp/PID$$.disc
-			if (( verbose )) 			# updated file was generated, keep here
+
+			if (( verbose > 1 )) 			# updated file was generated, keep here
 			then
 				echo "[INFO] discounted coverage info in: ${tfile##*/}.dcov"
-				mv /tmp/PID$$.disc ${tfile##*/}.dcov
 			fi
+
+			mv /tmp/PID$$.disc ${name##*/}.dcov
 		done
 	fi
 done
 
+state=0						# final state
 rm -f /tmp/PID$$.*
-if (( errors ))
+if (( strict ))				# fail if some coverage failed too
 then
-	exit 1
+	if (( errors + ut_errors ))
+	then
+		state=1
+	fi
+else						# not strict; fail only if unit tests themselves failed
+	if (( ut_errors ))
+	then
+		state=1
+	fi
 fi
-exit 0
+
+echo""
+if (( state ))
+then
+	echo "[FAIL] overall unit testing fails: coverage errors=$errors   unit test errors=$ut_errors"
+else
+	echo "[PASS] overall unit testing passes"
+fi
+exit $state
 
diff --git a/test/wormhole_static_test.c b/test/wormhole_static_test.c
new file mode 100644
index 0000000..7a4246f
--- /dev/null
+++ b/test/wormhole_static_test.c
@@ -0,0 +1,151 @@
+// : vi ts=4 sw=4 noet :
+/*
+==================================================================================
+        Copyright (c) 2019 Nokia 
+        Copyright (c) 2018-2019 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
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+	Mmemonic:	wormhole_static.c
+	Abstract:	Specific tests for wormhole. This module is included directly by
+				the test driver at compile time.
+
+	Author:		E. Scott Daniels
+	Date:		3 April 2019
+*/
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "../src/common/include/rmr.h"
+#include "../src/common/include/rmr_agnostic.h"
+
+
+/*
+	Note that the last tests in this function destroy the context and message so
+	any tests added MUST be ahead of those tests. 
+*/
+static int worm_test( ) {
+	uta_ctx_t* ctx;			// context needed to test load static rt
+	char	wbuf[1024];
+	int errors = 0;			// number errors found
+	int	i;
+
+	rmr_mbuf_t*	mbuf;		// mbuf to send to peer
+	int		whid = -1;
+	int		last_whid;
+
+	ctx = (uta_ctx_t *) malloc( sizeof( uta_ctx_t ) );
+	if( ctx == NULL ) {
+		fail_if_nil( ctx, "could not allocate dummy context" );
+		return 1;
+	}
+	memset( ctx, 0, sizeof( *ctx ) );
+	ctx->my_name = strdup( "tester" );
+
+	gen_rt( ctx );
+
+	whid = rmr_wh_open( NULL, NULL );
+	errors += fail_not_equal( whid, -1, "call to wh_open with invalid values did not return bad whid" );
+
+
+	whid = rmr_wh_open( ctx, NULL );
+	errors += fail_not_equal( whid, -1, "call to wh_open with invalid target did not return bad whid" );
+
+	whid = rmr_wh_open( ctx, "" );
+	errors += fail_not_equal( whid, -1, "call to wh_open with empty target did not return bad whid" );
+
+	whid = rmr_wh_open( ctx, "localhost:89219" );
+	errors += fail_if_equal( whid, -1, "call to wh_open with valid target failed" );
+
+	rmr_wh_close( ctx, 4 );					// test for coverage only; [5] should have nil pointer
+	rmr_wh_close( ctx, 50 );					// test for coverage only; more than allocated reference
+
+	last_whid = whid;
+	whid = rmr_wh_open( ctx, "localhost:89219" );
+	errors += fail_not_equal( whid, last_whid, "call to wh_open with duplicate target did not return the same whid" );
+
+	for( i = 0; i < 20; i++ ) {												// test ability to extend the table
+		snprintf( wbuf, sizeof( wbuf ), "localhost:864%02d", i );			// new address for each so whid is different
+		whid = rmr_wh_open( ctx, wbuf );
+		snprintf( wbuf, sizeof( wbuf ), "call to wh_open failed for iteration = %d", i );
+		errors += fail_if_equal( whid, -1, wbuf );
+		if( i ) {
+			snprintf( wbuf, sizeof( wbuf ), "call to wh_open for iteration = %d returned same whid: %d", i, whid );
+			errors += fail_if_equal( whid, last_whid, wbuf );
+		}
+
+		last_whid = whid;
+	}
+
+	rmr_wh_close( ctx, 3 );		// close one, then open a new one to verify that hole is found
+	whid = rmr_wh_open( ctx, "localhost:21961" );
+	errors += fail_not_equal( whid, 3, "attempt to fill in a hole didn't return expected" );
+
+	rmr_wh_send_msg( NULL, 0, NULL );			// tests for coverage
+	rmr_wh_send_msg( ctx, 0, NULL );
+
+	mbuf = rmr_alloc_msg( ctx, 2048 );			// get an muf to pass round
+	errors += fail_if_nil( mbuf, "unable to allocate mbuf for send tests (giving up on send tests)" );
+	while( mbuf ) {
+		if( !(mbuf = rmr_wh_send_msg( ctx, 50, mbuf )) ) {		// test for coverage
+			errors += fail_if_nil( mbuf, "send didn't return an mbuf (skip rest of send tests)" );
+			break;
+		}
+
+		mbuf = rmr_wh_send_msg( ctx, 4, mbuf );
+		errors += fail_not_equal( mbuf->state, RMR_OK, "valid wormhole send failed" );
+		errors += fail_not_equal( errno, 0, "errno after valid wormhole send was not 0" );
+
+		rmr_wh_close( ctx, 4 );
+		mbuf = rmr_wh_send_msg( ctx, 4, mbuf );
+		rmr_wh_send_msg( ctx, 4, mbuf );
+		errors += fail_not_equal( mbuf->state, RMR_ERR_WHID, "send on closed wormhole didn't set correct state in msg" );
+
+		break;
+	}
+
+
+	// WARNING:  these tests destroy the context, so they MUST be last
+	if( mbuf ) {			// only if we got an mbuf
+		errno = 0;
+		mbuf->header = NULL;
+		mbuf = rmr_wh_send_msg( ctx, 5, mbuf );		// coverage test on mbuf header check
+		errors += fail_not_equal( errno, EBADMSG, "wh_send didn't set errno after bad mbuf send" );
+		errors += fail_not_equal( mbuf->state, RMR_ERR_NOHDR, "send with bad header did now set msg state correctly" );
+
+		errno = 0;
+		wh_nuke( ctx );
+		ctx->wormholes = NULL;
+		mbuf = rmr_wh_send_msg( ctx, 4, mbuf );		// coverage test on mbuf header check
+		errors += fail_not_equal( errno, EINVAL, "wh_send didn't set errno after send without wormole reference" );
+		errors += fail_not_equal( mbuf->state, RMR_ERR_NOWHOPEN, "wh_send didn't set msg state after send without wormole reference" );
+
+		rmr_free_msg( mbuf );
+	}
+
+	if( ctx ) {
+		free( ctx->my_name );
+		free( ctx );
+	}
+
+	return !!errors;			// 1 or 0 regardless of count
+}