Add unit tests and changes related 51/2951/10
authorE. Scott Daniels <daniels@research.att.com>
Tue, 24 Mar 2020 16:28:06 +0000 (12:28 -0400)
committerE. Scott Daniels <daniels@research.att.com>
Wed, 25 Mar 2020 16:54:06 +0000 (12:54 -0400)
Base unit tests have been added with the means to generate
gcov coverage information and a small amounto of coverage
data.  Some changes were made to the code to make testing
coverage better and to fix identified issues.

Most important are the unique smart ptr changes; the
framework prototypes now require them to be shared pointers
as they are not released/reallocated (e.g. message Send()).

Issue-ID: RIC-148

Signed-off-by: E. Scott Daniels <daniels@research.att.com>
Change-Id: Ibc593ddc8687ce6d727bf6d3e2939c02f1e0afef

20 files changed:
CMakeLists.txt
Dockerfile [new file with mode: 0644]
build_rmr.sh [new file with mode: 0755]
examples/xapp_t1.cpp
rmr-version.yaml [new file with mode: 0644]
src/messaging/callback.cpp
src/messaging/callback.hpp
src/messaging/default_cb.cpp
src/messaging/default_cb.hpp
src/messaging/message.cpp
src/messaging/message.hpp
src/messaging/messenger.cpp
src/messaging/messenger.hpp
test/.gitignore [new file with mode: 0644]
test/Makefile [new file with mode: 0644]
test/parse_gcov.sh [new file with mode: 0755]
test/rmr_em.c [new file with mode: 0644]
test/scrub_gcov.sh [new file with mode: 0755]
test/unit_test.cpp [new file with mode: 0644]
test/unit_test.sh [new file with mode: 0755]

index d702d45..9e59870 100644 (file)
@@ -30,7 +30,7 @@ cmake_minimum_required( VERSION 3.5 )
 
 set( major_version "0" )               # should be automatically populated from git tag later, but until CI process sets a tag we use this
 set( minor_version "1" )
-set( patch_level "0" )
+set( patch_level "1" )
 
 set( install_root "${CMAKE_INSTALL_PREFIX}" )
 set( install_inc "include/ricxfcpp" )
@@ -199,6 +199,15 @@ if( DEV_PKG )
        target_include_directories( ricxfcpp_static PUBLIC "src/messenger" "src/xapp" )
 endif()
 
+# -------- unit testing -------------------------------------------------------
+enable_testing()
+add_test(
+       NAME drive_unit_tests
+       COMMAND bash ../test/unit_test.sh -q
+       WORKING_DIRECTORY ../test
+)
+
+
 # ------------- packaging -----------------------------------------------------
 
 # Define what should be installed, and where they should go. For dev package we install
diff --git a/Dockerfile b/Dockerfile
new file mode 100644 (file)
index 0000000..f9f9eab
--- /dev/null
@@ -0,0 +1,100 @@
+# vim: ts=4 sw=4 noet:
+#==================================================================================
+#      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:       Dockerfile
+#      Abstract:       This can be used to create a base environment for using the xAPP
+#                              framework.  It will install RMR and the framework libraries. It also
+#                              installs make and g++ so that it can be used as a builder environment.
+#
+#                              The unit tests are executed as a part of the build process; if they are
+#                              not passing then the build will fail.
+#
+#                              Building should be as simple as:
+#
+#                                      docker build -f Dockerfile -t ricxfcpp:[version]
+#
+#      Date:           23 March 2020
+#      Author:         E. Scott Daniels
+# --------------------------------------------------------------------------------------
+
+
+FROM nexus3.o-ran-sc.org:10004/bldr-ubuntu18-c-go:4-u18.04-nng as buildenv
+RUN mkdir /playpen
+
+RUN apt-get update && apt-get install -y cmake gcc make git g++ wget
+
+RUN mkdir /playpen/bin /playpen/factory /playpen/factory/src /playpen/factory/test
+ARG SRC=.
+
+WORKDIR /playpen
+# Install RMr (runtime and dev) from debian package cached on packagecloud.io
+ARG RMR_VER=3.6.2
+
+# if package cloud is actually working, this is preferred
+#
+#RUN wget -nv --content-disposition https://packagecloud.io/o-ran-sc/staging/packages/debian/stretch/rmr_${RMR_VER}_amd64.deb/download.deb
+#RUN wget -nv --content-disposition https://packagecloud.io/o-ran-sc/staging/packages/debian/stretch/rmr-dev_${RMR_VER}_amd64.deb/download.deb
+#RUN dpkg -i rmr_${RMR_VER}_amd64.deb
+#RUN dpkg -i rmr-dev_${RMR_VER}_amd64.deb
+#
+# else this:
+#
+COPY ${SRC}/build_rmr.sh /playpen/bin
+RUN bash /playpen/bin/build_rmr.sh -t ${RMR_VER}
+
+COPY ${SRC}/CMakeLists.txt /playpen/factory/
+COPY ${SRC}/src /playpen/factory/src/
+COPY ${SRC}/test /playpen/factory/test/
+COPY ${SRC}/examples /tmp/examples/
+
+#
+# Run unit tests
+#
+COPY ${SRC}/test/* /playpen/factory/test/
+RUN cd /playpen/factory/test; bash unit_test.sh
+
+# Go to the factory and build our stuff
+#
+ENV LD_LIBRARY_PATH=/usr/local/lib64:/usr/local/lib
+ENV C_INCLUDE_PATH=/usr/local/include
+RUN cd /playpen/factory; rm -fr .build; mkdir .build; cd .build; cmake .. -DDEV_PKG=1; make install; cmake .. -DDEV_PKG=0; make install
+
+
+# -----  final, smaller image ----------------------------------
+FROM ubuntu:18.04
+
+# must add compile tools to make it a builder environmnent. If a build environment isn't needed 
+# comment out the next line and reduce the image size by more than 180M.
+#
+RUN apt-get update && apt-get install -y --no-install-recommends make g++
+
+# if bash doesn't cut it for run_replay grab a real shell and clean up as much as we can
+RUN apt-get update; apt-get install -y ksh
+RUN rm -fr /var/lib/apt/lists 
+
+RUN mkdir -p /usr/local/include/ricxfcpp
+COPY --from=buildenv /usr/local/lib /usr/local/lib/
+COPY --from=buildenv /usr/local/include/ricxfcpp /usr/local/include/ricxfcpp/
+COPY --from=buildenv /usr/local/include/rmr /usr/local/include/rmr/
+
+ENV LD_LIBRARY_PATH=/usr/local/lib64:/usr/local/lib
+ENV C_INCLUDE_PATH=/usr/local/include
+WORKDIR /factory
+
+CMD [ "make" ]
diff --git a/build_rmr.sh b/build_rmr.sh
new file mode 100755 (executable)
index 0000000..905a15d
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/env ksh
+
+#      Mnemonic:       rmr_build.sh
+#      Abstract:       This will pull RMR from the repo, build and install. This
+#                              is en lieu of using wget to fetch the RMR package from some
+#                              repo and installing it.  The package method is preferred
+#                              but if that breaks this can be used in place of it.
+
+rmr_ver=${1:-3.6.2}
+
+# assume that we're in the proper directory
+set -e
+git clone "https://gerrit.o-ran-sc.org/r/ric-plt/lib/rmr"
+
+cd rmr
+git checkout $ver
+mkdir .build
+cd .build
+cmake .. -DDEV_PKG=1 -DPACK_EXTERNALS=1
+make install
+cmake .. -DDEV_PKG=0
+make install
+
index 8cce435..e525891 100644 (file)
@@ -52,7 +52,7 @@ long cbd_count = 0;
 long cb1_lastts = 0;
 long cb1_lastc = 0;
 
-void cb1( Messenger& mr, Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+void cb1( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
        long now;
        long total_count;
 
@@ -72,13 +72,13 @@ void cb1( Messenger& mr, Message& mbuf, int mtype, int subid, int len, Msg_compo
        }
 }
 
-void cb2( Messenger& mr, Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+void cb2( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
        //fprintf( stderr, "callback 2 got a message type = %d len = %d\n", mtype, len );
        //mbuf.Send_msg( 101, -1, 4, (unsigned char *) "HI\n" );                // send, including the trailing 0
        cb2_count++;
 }
 
-void cbd( Messenger& mr, Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+void cbd( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
        //fprintf( stderr, "default callback  got a message type = %d len = %d\n", mtype, len );
        cbd_count++;
 }
diff --git a/rmr-version.yaml b/rmr-version.yaml
new file mode 100644 (file)
index 0000000..5dd30dc
--- /dev/null
@@ -0,0 +1,3 @@
+# Communicate to CI which version of RMR to install in the build/vet environment
+---
+version: 3.6.2
index d7eacc6..d4d697d 100644 (file)
@@ -42,21 +42,17 @@ Callback::Callback( user_callback ufun, void* data ) {              // builder
 }
 
 /*
-       Wrecking ball.
+       there is nothing to be done from a destruction perspective, so no
+       destruction function needed at the moment.
 */
-Callback::~Callback() {
-       // nothing to do at the moment.
-}
 
 /*
        Drive_cb will invoke the callback and pass along the stuff passed here.
 */
-void Callback::Drive_cb( Messenger& mr, Message& m ) {
-       if( user_fun == NULL ) {
-               return;
+void Callback::Drive_cb( Message& m ) {
+       if( user_fun != NULL ) {
+               user_fun( m, m.Get_mtype(), m.Get_subid(), m.Get_len(), m.Get_payload(),  udata );
        }
-
-       user_fun( mr, m, m.Get_mtype(), m.Get_subid(), m.Get_len(), m.Get_payload(),  udata );
 }
 
 
index 2b7813d..b8e9dfa 100644 (file)
@@ -35,8 +35,7 @@ class Messenger;
 class Message;
 #include "message.hpp"
 
-typedef void(*user_callback)( Messenger& mr, Message& m, int mtype, int subid, int payload_len, Msg_component payload, void* usr_data );
-                                                               //std::unique_ptr<unsigned char,unfreeable> payload, void* usr_data );
+typedef void(*user_callback)( Message& m, int mtype, int subid, int payload_len, Msg_component payload, void* usr_data );
 
 class Callback {
 
@@ -46,8 +45,7 @@ class Callback {
 
        public:
                Callback( user_callback, void* data );                                  // builder
-               ~Callback();                                                                                    // destroyer
-               void Drive_cb( Messenger& mr, Message& m );             // invoker
+               void Drive_cb( Message& m );                                                    // invoker
 };
 
 
index d7cfdbf..1083293 100644 (file)
@@ -46,7 +46,7 @@
        The mr paramter is obviously ignored, but to add this as a callback
        the function sig must match.
 */
-void Health_ck_cb( Messenger& mr, Message& mbuf, int mtype, int sid, int len, Msg_component payload, void* data ) {
+void Health_ck_cb( Message& mbuf, int mtype, int sid, int len, Msg_component payload, void* data ) {
        unsigned char response[128];
 
        snprintf( (char* ) response, sizeof( response ), "OK\n" );
index fdc084a..2e96003 100644 (file)
@@ -33,7 +33,7 @@
 #define _DEF_CB_H
 
 
-void Health_ck_cb( Messenger& mr, Message& mbuf, int mtype, int sid, int len, Msg_component payload, void* data );
+void Health_ck_cb( Message& mbuf, int mtype, int sid, int len, Msg_component payload, void* data );
 
 
 #endif
index 0c6b262..d149cc0 100644 (file)
@@ -97,28 +97,30 @@ std::unique_ptr<unsigned char> Message::Copy_payload( ){
 std::unique_ptr<unsigned char> Message::Get_meid(){
        unsigned char* m = NULL;
 
-       if( m != NULL ) {
-               m = (unsigned char *) malloc( sizeof( unsigned char ) * RMR_MAX_MEID );
-               rmr_get_meid( mbuf, m );
-       }
+       m = (unsigned char *) malloc( sizeof( unsigned char ) * RMR_MAX_MEID );
+       rmr_get_meid( mbuf, m );
 
        return std::unique_ptr<unsigned char>( m );
 }
 
+/*
+       Return the total size of the payload (the amount that can be written to
+       as opposed to the portion of the payload which is currently in use.
+       If mbuf isn't valid (nil, or message has a broken header) the return
+       will be -1.
+*/
 int Message::Get_available_size(){
-       if( mbuf != NULL ) {
-               return rmr_payload_size( mbuf );
-       }
-
-       return 0;
+       return rmr_payload_size( mbuf );                // rmr can handle a nil pointer
 }
 
 int    Message::Get_mtype(){
+       int rval = INVALID_MTYPE;
+
        if( mbuf != NULL ) {
-               return mbuf->mtype;
+               rval = mbuf->mtype;
        }
 
-       return INVALID_MTYPE;
+       return rval;
 }
 
 /*
@@ -128,6 +130,8 @@ std::unique_ptr<unsigned char> Message::Get_src(){
        unsigned char* m = NULL;
 
        m = (unsigned char *) malloc( sizeof( unsigned char ) * RMR_MAX_SRC );
+       memset( m, 0, sizeof( unsigned char ) * RMR_MAX_SRC );
+
        if( m != NULL ) {
                rmr_get_src( mbuf, m );
        }
@@ -144,19 +148,27 @@ int       Message::Get_state( ){
 }
 
 int    Message::Get_subid(){
+       int     rval = INVALID_SUBID;
+
        if( mbuf != NULL ) {
-               return mbuf->sub_id;
+               rval =mbuf->sub_id;
        }
 
-       return INVALID_SUBID;
+       return rval;
 }
 
+/*
+       Return the amount of the payload (bytes) which is used. See
+       Get_available_size() to get the total usable space in the payload.
+*/
 int    Message::Get_len(){
+       int rval = 0;
+
        if( mbuf != NULL ) {
-               return mbuf->len;
+               rval = mbuf->len;
        }
 
-       return 0;
+       return rval;
 }
 
 /*
@@ -175,7 +187,7 @@ Msg_component Message::Get_payload(){
        return NULL;
 }
 
-void Message::Set_meid( std::unique_ptr<unsigned char> new_meid ) {
+void Message::Set_meid( std::shared_ptr<unsigned char> new_meid ) {
        if( mbuf != NULL ) {
                rmr_str2meid( mbuf, (unsigned char *) new_meid.get() );
        }
@@ -187,6 +199,12 @@ void Message::Set_mtype( int new_type ){
        }
 }
 
+void Message::Set_len( int new_len ){
+       if( mbuf != NULL  && new_len >= 0 ) {
+               mbuf->len = new_len;
+       }
+}
+
 void Message::Set_subid( int new_subid ){
        if( mbuf != NULL ) {
                mbuf->sub_id = new_subid;
@@ -240,9 +258,16 @@ bool Message::Send( int mtype, int subid, int payload_len, unsigned char* payloa
                return false;
        }
 
-       mbuf->mtype = mtype;
-       mbuf->sub_id = subid;
-       mbuf->len = payload_len;
+       if( mtype != NO_CHANGE ) {
+               mbuf->mtype = mtype;
+       }
+       if( subid != NO_CHANGE ) {
+               mbuf->sub_id = subid;
+       }
+
+       if( payload_len != NO_CHANGE ) {
+               mbuf->len = payload_len;
+       }
 
        if( payload != NULL ) {                 // if we have a payload, ensure msg has room, realloc if needed, then copy
                mbuf = rmr_realloc_payload( mbuf, payload_len, RMR_NO_COPY, RMR_NO_CLONE );             // ensure message is large enough
@@ -271,7 +296,7 @@ bool Message::Send( int mtype, int subid, int payload_len, unsigned char* payloa
        The second form of the call allows for a stack allocated buffer (e.g. char foo[120]) to
        be passed as the payload.
 */
-bool Message::Send_response(  int mtype, int subid, int response_len, std::unique_ptr<unsigned char> response ) {
+bool Message::Send_response(  int mtype, int subid, int response_len, std::shared_ptr<unsigned char> response ) {
        return Send( mtype, subid, response_len, response.get(), RESPONSE );
 }
 
@@ -282,12 +307,12 @@ bool Message::Send_response(  int mtype, int subid, int response_len, unsigned c
 /*
        These allow a response message to be sent without changing the mtype/subid.
 */
-bool Message::Send_response(  int response_len, std::unique_ptr<unsigned char> response ) {
-       return Send( NOCHANGE, NOCHANGE, response_len, response.get(), RESPONSE );
+bool Message::Send_response(  int response_len, std::shared_ptr<unsigned char> response ) {
+       return Send( NO_CHANGE, NO_CHANGE, response_len, response.get(), RESPONSE );
 }
 
 bool Message::Send_response(  int response_len, unsigned char* response ) {
-       return Send( NOCHANGE, NOCHANGE, response_len, response, RESPONSE );
+       return Send( NO_CHANGE, NO_CHANGE, response_len, response, RESPONSE );
 }
 
 
@@ -300,7 +325,7 @@ bool Message::Send_response(  int response_len, unsigned char* response ) {
        Return is a new mbuf suitable for sending another message, or the original buffer with
        a bad state sent if there was a failure.
 */
-bool Message::Send_msg(  int mtype, int subid, int payload_len, std::unique_ptr<unsigned char> payload ) {
+bool Message::Send_msg(  int mtype, int subid, int payload_len, std::shared_ptr<unsigned char> payload ) {
        return Send( mtype, subid, payload_len, payload.get(), MESSAGE );
 }
 
@@ -311,10 +336,10 @@ bool Message::Send_msg(  int mtype, int subid, int payload_len, unsigned char* p
 /*
        Similar send functions that allow the message type/subid to remain unchanged
 */
-bool Message::Send_msg(  int payload_len, std::unique_ptr<unsigned char> payload ) {
-       return Send( NOCHANGE, NOCHANGE, payload_len, payload.get(), MESSAGE );
+bool Message::Send_msg(  int payload_len, std::shared_ptr<unsigned char> payload ) {
+       return Send( NO_CHANGE, NO_CHANGE, payload_len, payload.get(), MESSAGE );
 }
 
 bool Message::Send_msg(  int payload_len, unsigned char* payload ) {
-       return Send( NOCHANGE, NOCHANGE, payload_len, payload, MESSAGE );
+       return Send( NO_CHANGE, NO_CHANGE, payload_len, payload, MESSAGE );
 }
index 8c06ed1..ce654d5 100644 (file)
 
 class Message {
        private:
-               static const int        NOCHANGE = -99; // internal constant indicating no change to something
-
                rmr_mbuf_t*     mbuf;                                   // the underlying RMR message buffer
                void*           mrc;                                    // message router context
                std::shared_ptr<char> psp;                      // shared pointer to the payload to give out
 
        public:
+               static const int        NO_CHANGE = -99;                        // indicates no change to a send/reply parameter
                static const int        INVALID_MTYPE = -1;
                static const int        INVALID_STATUS = -1;
                static const int        INVALID_SUBID = -2;
@@ -74,7 +73,7 @@ class Message {
 
                std::unique_ptr<unsigned char>  Copy_payload( );                // copy the payload; deletable smart pointer
 
-               std::unique_ptr<unsigned char> Get_meid();              // returns a copy of the meid bytes
+               std::unique_ptr<unsigned char> Get_meid();                              // returns a copy of the meid bytes
                int Get_available_size();
                int     Get_len();
                int     Get_mtype();
@@ -83,22 +82,23 @@ class Message {
                int     Get_state( );
                int     Get_subid();
 
-               void Set_meid( std::unique_ptr<unsigned char> new_meid );
+               void Set_meid( std::shared_ptr<unsigned char> new_meid );
                void Set_mtype( int new_type );
                void Set_subid( int new_subid );
+               void Set_len( int new_len );
 
                bool Reply( );
                bool Send( );
                bool Send( int mtype, int subid, int payload_len, unsigned char* payload, int stype );
 
-               bool Send_msg( int mtype, int subid, int payload_len, std::unique_ptr<unsigned char> payload );
+               bool Send_msg( int mtype, int subid, int payload_len, std::shared_ptr<unsigned char> payload );
                bool Send_msg( int mtype, int subid, int payload_len, unsigned char* payload );
-               bool Send_msg( int payload_len, std::unique_ptr<unsigned char> payload );
+               bool Send_msg( int payload_len, std::shared_ptr<unsigned char> payload );
                bool Send_msg( int payload_len, unsigned char* payload );
 
-               bool Send_response( int mtype, int subid, int payload_len, std::unique_ptr<unsigned char> response );
+               bool Send_response( int mtype, int subid, int payload_len, std::shared_ptr<unsigned char> response );
                bool Send_response( int mtype, int subid, int payload_len, unsigned char* response );
-               bool Send_response( int payload_len, std::unique_ptr<unsigned char> response );
+               bool Send_response( int payload_len, std::shared_ptr<unsigned char> response );
                bool Send_response( int payload_len, unsigned char* response );
 };
 
index d4033c3..6951f7d 100644 (file)
@@ -148,8 +148,8 @@ void Messenger::Listen( ) {
                                        sel_cb = mi->second;                                                            // override with user callback
                                }
                                if( sel_cb != NULL ) {
-                                       sel_cb->Drive_cb( *this, *m );                                          // drive the selected one
-                                       mbuf = NULL;                                                                            // not safe to use after given to cb
+                                       sel_cb->Drive_cb( *m );                                                 // drive the selected one
+                                       mbuf = NULL;                                                                    // not safe to use after given to cb
                                }
                        } else {
                                if( mbuf->state != RMR_ERR_TIMEOUT ) {
@@ -190,10 +190,10 @@ void Messenger::Stop( ) {
        RMR messages must be released by RMR as there might be transport
        buffers that have to be dealt with. Every callback is expected to
        call this function when finished with the message.
-*/
 void Messenger::Release_mbuf( void* vmbuf ) {
        rmr_free_msg( (rmr_mbuf_t *)  vmbuf );
 }
+*/
 
 /*
        Wait for clear to send.
@@ -216,16 +216,18 @@ void Messenger::Release_mbuf( void* vmbuf ) {
 */
 bool Messenger::Wait_for_cts( int max_wait ) {
        bool block_4ever;
+       bool    state = false;
 
        block_4ever = max_wait == 0;
        while( block_4ever || max_wait > 0 ) {
                if( rmr_ready( mrc ) ) {
-                       return true;
+                       state = true;
+                       break;
                }
 
                sleep( 1 );
                max_wait--;
        }
 
-       return false;
+       return state;
 }
index 0fa7899..c8e4f13 100644 (file)
@@ -71,7 +71,7 @@ class Messenger {
                void Listen( );                                                                                                 // lisen driver
                std::unique_ptr<Message> Receive( int timeout );                                // receive 1 message
                void Stop( );                                                                                                   // force to stop
-               void Release_mbuf( void* vmbuf );
+               //void Release_mbuf( void* vmbuf );
                bool Wait_for_cts( int max_wait );
 };
 
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644 (file)
index 0000000..9903b8d
--- /dev/null
@@ -0,0 +1,4 @@
+core
+*.gcov 
+*.gcda 
+*.gcno
diff --git a/test/Makefile b/test/Makefile
new file mode 100644 (file)
index 0000000..fcbf361
--- /dev/null
@@ -0,0 +1,21 @@
+
+coverage_opts = -ftest-coverage -fprofile-arcs
+
+binaries = unit_test 
+
+# RMR emulation
+rmr_em.o::     rmr_em.c
+       cc -g rmr_em.c -c
+
+unit_test:: unit_test.cpp rmr_em.o
+       # do NOT link the xapp lib; we include all modules in the test programme
+       g++ -g $(coverage_opts) -I ../src/messaging unit_test.cpp -o unit_test rmr_em.o  -lpthread
+
+# prune gcov files generated by system include files
+clean::
+       rm -f *.h.gcov *.c.gcov
+
+# ditch anything that can be rebuilt
+nuke::
+       rm -f *.a *.o *.gcov *.gcda *.gcno core a.out $(binaries)
+
diff --git a/test/parse_gcov.sh b/test/parse_gcov.sh
new file mode 100755 (executable)
index 0000000..d69a88b
--- /dev/null
@@ -0,0 +1,180 @@
+#!/usr/bin/env bash
+# vim: ts=4 sw=4 noet :
+
+#==================================================================================
+#       Copyright (c) 2020 Nokia
+#       Copyright (c) 2020 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.
+#==================================================================================
+
+#
+#      Parse the .gcov file and discount any unexecuted lines which are in if()
+#      blocks which 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 deficiencies.
+#
+#      In verbose mode, the .gcov file is written to stdout and any unexecuted
+#      line which is discounted is marked with ===== replacing the ##### marking
+#      that gcov wrote.
+#
+#      The return value is 0 for pass; non-zero for fail.
+#
+function discount_ck {
+       typeset f="$1"
+
+       mct=80                                                  # force minimum coverage threshold for passing
+
+       if [[ ! -f $f ]]
+       then
+               if [[ -f ${f##*/} ]]
+               then
+                       f=${f##*/}
+               else
+                       echo "cant find: $f"
+                       return
+               fi
+       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=$chatty \
+               -v replace_flags=$replace_flags \
+       '
+       function spit_line( ) {
+               if( chatty ) {
+                       printf( "%s\n", $0 )
+               }
+       }
+
+       /-:/ {                                          # skip unexecutable lines
+               spit_line()
+               seq++                                   # allow blank lines in a sequence group
+               next
+       }
+
+       {
+               nexec++                                 # number of executable lines
+       }
+
+       /#####:/ {
+               unexec++;
+               if( $2+0 != seq+1 ) {
+                       prev_malloc = 0
+                       prev_if = 0
+                       seq = 0
+                       spit_line()
+                       next
+               }
+
+               if( prev_if && prev_malloc ) {
+                       if( prev_malloc ) {
+                               #printf( "allow discount: %s\n", $0 )
+                               if( replace_flags ) {
+                                       gsub( "#####", "    1", $0 )
+                                       //gsub( "#####", "=====", $0 )
+                               }
+                               discount++;
+                       }
+               }
+
+               seq++;;
+               spit_line()
+               next;
+       }
+
+       /if[(].*alloc.*{/ {                     # if( (x = malloc( ... )) != NULL ) or if( (p = sym_alloc(...)) != NULL )
+               seq = $2+0
+               prev_malloc = 1
+               prev_if = 1
+               spit_line()
+               next
+       }
+
+       /if[(].* == NULL/ {                             # a nil check likely not easily forced if it wasnt driven
+               prev_malloc = 1
+               prev_if = 1
+               spit_line()
+               seq = $2+0
+               next
+       }
+
+       /if[(]/ {
+               if( seq+1 == $2+0 && prev_malloc ) {            // malloc on previous line
+                       prev_if = 1
+               } else {
+                       prev_malloc = 0
+                       prev_if = 0
+               }
+               spit_line()
+               next
+       }
+
+       /alloc[(]/ {
+               seq = $2+0
+               prev_malloc = 1
+               spit_line()
+               next
+       }
+
+       {
+               spit_line()
+       }
+
+       END {
+               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 ? cfail : "PASS"
+               rc = adj_cov < module_cov_target ? 1 : 0
+               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 )
+       }
+       ' $f
+}
+
+# ----------------------------------------------------------------------
+show_all=1                     # turn off to hide passing modules (-q)
+chatty=0                       # -v turns on to provide more info when we do speak
+
+while [[ $1 == "-"* ]]
+do
+       case $1 in 
+               -q)     show_all=0;;
+               -v)     chatty=1;;
+
+               *)      echo "unrecognised option: $1"
+                       echo "usage: $0 [-q] gcov-file-list"
+                       exit 1
+                       ;;
+       esac
+       shift
+done
+
+
+while [[ -n $1 ]]
+do
+       discount_ck $1
+       shift
+done
diff --git a/test/rmr_em.c b/test/rmr_em.c
new file mode 100644 (file)
index 0000000..581248e
--- /dev/null
@@ -0,0 +1,236 @@
+// vim: ts=4 sw=4 noet :
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       rmr_em.c
+       Abstract:       RMR emulation for testing
+
+       Date:           20 March        
+       Author:         E. Scott Daniels
+*/
+
+#include <unistd.h>
+#include <string.h>
+#include <malloc.h>
+
+
+/*
+       CAUTION:  this is a copy of what is in RMR and it is needed as some of the framework
+       functions access the 'public' fields like state and mtype.
+*/
+typedef struct {
+    int state;                  // state of processing
+    int mtype;                  // message type
+    int len;                    // length of data in the payload (send or received)
+    unsigned char* payload;     // transported data
+    unsigned char* xaction;     // pointer to fixed length transaction id bytes
+    int sub_id;                 // subscription id
+    int     tp_state;           // transport state (errno) valid only if state != RMR_OK, and even then may not be valid
+
+                                // these things are off limits to the user application
+    void*   tp_buf;             // underlying transport allocated pointer (e.g. nng message)
+    void*   header;             // internal message header (whole buffer: header+payload)
+    unsigned char* id;          // if we need an ID in the message separate from the xaction id
+    int     flags;              // various MFL_ (private) flags as needed
+    int     alloc_len;          // the length of the allocated space (hdr+payload)
+
+    void*   ring;               // ring this buffer should be queued back to
+    int     rts_fd;             // SI fd for return to sender
+
+    int     cookie;             // cookie to detect user misuse of free'd msg
+} rmr_mbuf_t;
+
+typedef struct {
+       char meid[32];
+       char src[32];
+} header_t;
+
+
+void* rmr_init( char* port, int flags ) {
+       return malloc( sizeof( char ) * 100 );
+}
+
+rmr_mbuf_t* rmr_alloc_msg( void* mrc, int payload_len ) {
+       rmr_mbuf_t*     mbuf;
+       char* p;                                // the tp buffer; also the payload
+       header_t* h;
+       int len;
+
+       len = (sizeof( char ) * payload_len) + sizeof( header_t );
+       p = (char *) malloc( len );
+       if( p == NULL ) {
+               return NULL;
+       }
+       h = (header_t *) p;
+       memset( p, 0, len );
+
+       mbuf = (rmr_mbuf_t *) malloc( sizeof( rmr_mbuf_t ) );
+       if( mbuf == NULL ) {
+               free( p );
+               return NULL;
+       }
+       memset( mbuf, 0, sizeof( rmr_mbuf_t ) );
+
+       mbuf->tp_buf = p;
+       mbuf->payload = (char *)p + sizeof( header_t );
+
+
+       mbuf->len = 0;
+       mbuf->alloc_len = payload_len;
+       mbuf->payload = (void *) p;
+       strncpy( h->src, "host:ip", 8 );
+       strncpy( h->meid, "EniMeini", 9 );
+
+       return mbuf;
+}
+
+void rmr_free_msg( rmr_mbuf_t* mbuf ) {
+       if( mbuf ) {
+               if( mbuf->tp_buf ) {
+                       free( mbuf->tp_buf );
+               }
+               free( mbuf );
+       }
+}
+
+char* rmr_get_meid( rmr_mbuf_t* mbuf, char* m ) {
+       header_t* h;
+
+       if( mbuf != NULL ) {
+               if( m == NULL ) {
+                       m = (char *) malloc( sizeof( char ) * 32 );             
+               }
+               h = (header_t *) mbuf->tp_buf;
+               memcpy( m, h->meid, 32 );
+       }
+
+       return m;
+}
+
+int rmr_payload_size( rmr_mbuf_t* mbuf ) {
+
+       if( mbuf != NULL ) {
+               return mbuf->alloc_len;
+       }
+
+       return 0;
+}
+
+char *rmr_get_src( rmr_mbuf_t* mbuf, char *m ) {
+       header_t*  h;
+
+       if( mbuf != NULL ) {
+               if( m == NULL ) {
+                       m = (char *) malloc( sizeof( char ) * 32 );             
+               }
+               h = (header_t *) mbuf->tp_buf;
+               memcpy( m, h->src, 32 );
+       }
+
+       return m;
+}
+
+int rmr_str2meid( rmr_mbuf_t* mbuf, unsigned char* s ) {
+       header_t*  h;
+
+       if( mbuf != NULL ) {
+               if( s == NULL ) {
+                       return 1;
+               }
+
+               if( strlen( s ) > 31 ) {
+                       return 1;
+               }
+
+               h = (header_t *) mbuf->tp_buf;
+               strncpy( h->meid, s, 32 );
+               return 0;
+       }
+
+       return 1;
+}
+
+rmr_mbuf_t* rmr_send_msg( void* mrc, rmr_mbuf_t* mbuf ) {
+
+       if( mbuf != NULL ) {
+               mbuf->state = 0;        
+       }
+       
+       return mbuf;
+}
+
+rmr_mbuf_t* rmr_rts_msg( void* mrc, rmr_mbuf_t* mbuf ) {
+
+       if( mbuf != NULL ) {
+               mbuf->state = 0;        
+       }
+       
+       return mbuf;
+}
+
+rmr_mbuf_t* rmr_realloc_payload( rmr_mbuf_t* mbuf, int payload_len, int copy, int clone ) {             // ensure message is large enough
+       return rmr_alloc_msg( NULL, payload_len );
+}
+
+void rmr_close( void*  mrc ) {
+       return;
+}
+
+rmr_mbuf_t* rmr_torcv_msg( void* mrc, rmr_mbuf_t* mbuf, int timeout ) {
+       static int max2receive = 500;
+       static int mtype = 0;
+
+       if( mbuf == NULL ) {
+               mbuf = rmr_alloc_msg( NULL, 2048 );
+       }
+
+       if( max2receive <= 0 ) {
+               mbuf->state = 12;
+               mbuf->len = 0;
+               mbuf->mtype = -1;
+               mbuf->sub_id = -1;
+       }
+
+       max2receive--;
+
+       mbuf->state = 0;
+       mbuf->len = 80;
+       mbuf->mtype = mtype;
+       mbuf->sub_id = -1;
+
+       mtype++;
+       if( mtype > 100 ) {
+               mtype = 0;
+       }
+
+       return mbuf;
+}
+
+int rmr_ready( void* mrc ) {
+       static int state = 0;
+
+       if( ! state )  {
+               state = 1;
+               return 0;
+       } 
+
+       return 1;
+}
+
diff --git a/test/scrub_gcov.sh b/test/scrub_gcov.sh
new file mode 100755 (executable)
index 0000000..349fca2
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+# vim: ts=4 sw=4 noet:
+
+#==================================================================================
+#       Copyright (c) 2020 Nokia
+#       Copyright (c) 2020 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:       scrub_gcov.sh
+#      Abstract:       Gcov (sadly) outputs for any header file that we pull.
+#                              this scrubs any gcov that doesnt look like it belongs
+#                              to our code.
+#
+#      Date:           24 March 2020
+#      Author:         E. Scott Daniels
+# -----------------------------------------------------------------------------
+
+
+# Make a list of our modules under test so that we don't look at gcov
+# files that are generated for system lib headers in /usr/*
+# (bash makes the process of building a list of names  harder than it 
+# needs to be, so use caution with the printf() call.)
+#
+function mk_list {
+       for f in *.gcov
+       do
+               if ! grep -q "Source:\.\./src"  $f
+               then
+                       printf "$f "            # do NOT use echo or add \n!
+               fi
+       done 
+}
+
+
+list=$( mk_list )
+if [[ -n $list ]]
+then
+       rm $list
+fi
diff --git a/test/unit_test.cpp b/test/unit_test.cpp
new file mode 100644 (file)
index 0000000..7ba91d5
--- /dev/null
@@ -0,0 +1,229 @@
+// vim: ts=4 sw=4 noet :
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       Unit_test.cpp
+       Abstract:       This is the unit test driver for the C++ xAPP framework
+
+       Date:           20 March 2020
+       Author:         E. Scott Daniels
+*/
+
+#include <memory>
+
+
+#include "../src/messaging/callback.hpp"
+#include "../src/messaging/default_cb.hpp"
+#include "../src/messaging/message.hpp"
+#include "../src/messaging/messenger.hpp"
+#include "../src/messaging/msg_component.hpp"
+#include "../src/xapp/xapp.hpp"
+
+#include "../src/messaging/callback.cpp"
+#include "../src/messaging/default_cb.cpp"
+#include "../src/messaging/message.cpp"
+#include "../src/messaging/messenger.cpp"
+#include "../src/xapp/xapp.cpp"
+
+// callback error counts are global for ease
+int err_cb1 = 0;
+int err_cb2 = 0;
+int err_cbd = 0;
+
+int good_cb1 = 0;
+int good_cb2 = 0;
+int good_cbd = 0;
+
+/*
+       callback functions to register; driven as we "receive" messages (the RMR emulation package 
+       will generate a message every time the receive function is called).
+*/
+void cb1( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+       if( mtype != 1 ) {      // should only be driven for type 1 messages
+               err_cb1++;
+       } else {
+               good_cb1++;
+       }
+}
+void cb2( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+       if( mtype != 2 ) {      // should only be driven for type 2 messages
+               err_cb2++;
+       } else {
+               good_cb2++;
+       }
+}
+void cbd( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+       if( mtype > 0 && mtype < 3 ) {                          // should only be driven for types that arent 1 or 2 
+               if( err_cbd < 10 ) {
+                       fprintf( stderr, "<FAIL> cbd: bad message type: %d\n", mtype );
+               }
+               err_cbd++;
+       } else {
+               good_cbd++;
+       }
+}
+
+void killer( std::shared_ptr<Xapp> x ) {
+       fprintf( stderr, ">>>> killer is waiting in the shadows\n" );
+       sleep( 5 );
+       fprintf( stderr, ">>>> killer is on the loose\n" );
+       x->Halt();
+}
+
+int main( int argc, char** argv ) {
+       std::thread* tinfo;                                     // we'll start a thread that will shut things down after a few seconds
+       std::unique_ptr<Message> msg;
+       std::shared_ptr<Xapp> x;
+       Msg_component payload;
+       std::unique_ptr<unsigned char> ucs;
+       unsigned char* new_payload;
+       std::shared_ptr<unsigned char> new_p_ref;       // reference to payload to pass to send functions
+       char*   port = (char *) "4560";
+       int             ai = 1;                                                 // arg processing index
+       int             nthreads = 2;                                   // ensure the for loop is executed in setup
+       int             i;
+       int             errors = 0;
+
+       ai = 1;
+       while( ai < argc ) {                            // very simple flag processing (no bounds/error checking)
+               if( argv[ai][0] != '-' )  {
+                       break;
+               }
+
+               switch( argv[ai][1] ) {                 // we only support -x so -xy must be -x -y
+                       case 'p': 
+                               port = argv[ai+1];      
+                               ai++;
+                               break;
+
+                       case 't':
+                               nthreads = atoi( argv[ai+1] );
+                               ai++;
+                               break;
+               }
+
+               ai++;
+       }
+       
+       x = std::shared_ptr<Xapp>( new Xapp( port, true ) );
+       x->Add_msg_cb( 1, cb1, NULL );
+       x->Add_msg_cb( 2, cb2, NULL );
+       x->Add_msg_cb( -1, cbd, NULL );
+
+       msg = x->Alloc_msg( 2048 );
+       payload = msg->Get_payload();
+       msg->Set_len( 128 );
+       msg->Set_mtype( 100 );
+       msg->Set_subid( -10 );
+
+       ucs = msg->Copy_payload( );
+       if( ucs == NULL ) {
+               fprintf( stderr, "<FAIL> expected pointer to copy payload but got nil\n" );
+               errors++;
+       }
+
+       ucs = msg->Get_meid();
+       if( ucs == NULL ) {
+               fprintf( stderr, "<FAIL> expected pointer to meid copy but got nil\n" );
+               errors++;
+       }
+
+       ucs = msg->Get_src();
+       if( ucs == NULL ) {
+               fprintf( stderr, "<FAIL> expected pointer to src copy but got nil\n" );
+               errors++;
+       }
+
+       i = msg->Get_available_size();
+       if( i != 2048 ) {
+               fprintf( stderr, "<FAIL> len expected payload avail size of 2048 but got %d\n", i );
+               errors++;
+       }
+
+       i = msg->Get_mtype();
+       if( i != 100 ) {
+               fprintf( stderr, "<FAIL> expected mtype of 100 but got %d\n", i );
+               errors++;
+       }
+
+       i = msg->Get_state( );
+       if( i != 0 ) {
+               fprintf( stderr, "<FAIL> expected state of 0 but got %d\n", i );
+               errors++;
+       }
+
+       i = msg->Get_subid();
+       if( i != -10 ) {
+               fprintf( stderr, "<FAIL> expected subid of -10 but got %d\n", i );
+               errors++;
+       }
+
+       i = msg->Get_len();
+       if( i != 128 ) {
+               fprintf( stderr, "<FAIL> len expected 128 but got %d\n", i );
+               errors++;
+       }
+
+       msg->Send();                            // generic send as is functions
+       msg->Reply();
+
+       new_payload = (unsigned char *) malloc( sizeof( unsigned char ) * 2048 );       // a large payload
+       memset( new_payload, 0 , sizeof( unsigned char ) * 2048 );
+       new_p_ref = std::shared_ptr<unsigned char>( new_payload );                                      // reference it for send calls
+
+       msg->Set_meid( new_p_ref );
+
+       msg->Send_msg( 255, new_p_ref );                                                                                        // send without changing the message type/subid from above
+       msg->Send_msg( 255, new_payload );                                                                                      // drive the alternate prototype
+       msg->Send_msg( 100, 1, 128, new_p_ref );                                                                        // send using just 128 bytes of payload
+       msg->Send_msg( 100, 1, 128, new_payload );                                                                      // drive with alternate prototype
+
+       msg->Set_len( 128 );
+       msg->Set_mtype( 100 );
+       msg->Set_subid( -10 );
+
+       msg->Send_response( 100, new_p_ref );                                                                           // send a response (rts) with establisehd message type etc
+       msg->Send_response( 100, new_payload );
+       msg->Send_response( 100, 10, 100, new_p_ref );
+       msg->Send_response( 100, 10, 100, new_payload );
+
+
+       msg = NULL;                                                             // should drive the message destroyer for coverage
+
+       msg = x->Receive( 2000 );
+       if( msg == NULL ) {
+               fprintf( stderr, "<FAIL> expected message from receive but got nil\n" );
+               errors++;
+       }
+
+       tinfo = new std::thread;                                // start killer thread to terminate things so that run doesn't hang forever
+       tinfo = new std::thread( killer, x );
+
+       x->Run( nthreads );
+       x->Halt();                      // drive for coverage
+
+       if( err_cb1 + err_cb2 + err_cbd > 0 ) {
+               fprintf( stderr, "<FAIL> one or more callbacks reported an error:  [%d] [%d] [%d]\n", err_cb1, err_cb2, err_cbd );
+               fprintf( stderr, "<INFO> callback good values:  [%d] [%d] [%d]\n", good_cb1, good_cb2, good_cbd );
+               errors++;
+       }
+
+       return errors > 0;
+}
diff --git a/test/unit_test.sh b/test/unit_test.sh
new file mode 100755 (executable)
index 0000000..9e0564b
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+# vim: ts=4 sw=4 noet:
+
+#==================================================================================
+#       Copyright (c) 2020 Nokia
+#       Copyright (c) 2020 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:       unit_test.sh
+#      Abstract:       This drives the unit tests and combs out the needed .gcov
+#                              files which are by some magic collected for Sonar.
+#
+#      Date:           23 March 2020
+#      Author:         E. Scott Daniels
+# -----------------------------------------------------------------------------
+
+
+# Make a list of our modules under test so that we don't look at gcov
+# files that are generated for system lib headers in /usr/*
+# (bash makes the process of building a list of names  harder than it 
+# needs to be, so use caution with the printf() call.)
+#
+function mk_list {
+       grep -l "Source:\.\./src"  *.gcov | while read f
+       do
+               printf "$f "            # do NOT use echo or add \n!
+       done 
+}
+
+function abort_if_error {
+       if (( $1 == 0 ))
+       then
+               return
+       fi
+
+       if [[ -n /tmp/PID$$.log ]]
+       then
+               $spew /tmp/PID$$.log
+       fi
+       echo "abort: $2"
+
+       rm -f /tmp/PID$$.*
+       exit 1
+}
+
+# -------------------------------------------------------------------------
+
+spew="cat"                                     # default to dumping all make output on failure (-q turns it to ~40 lines)
+
+while [[ $1 == "-"* ]]
+do
+       case $1 in
+               -q) spew="head -40";;
+               -v)     spew="cat";;
+       esac
+
+       shift
+done
+
+make nuke >/dev/null
+make unit_test >/tmp/PID$$.log 2>&1
+abort_if_error $? "unable to make"
+
+spew="cat"
+./unit_test >/tmp/PID$$.log 2>&1
+abort_if_error $? "unit test failed"
+
+gcov unit_test >/tmp/PID$$.gcov_log 2>&1       # suss out our gcov files
+./scrub_gcov.sh                                                                # remove cruft
+
+list=$( mk_list )
+./parse_gcov.sh $list                                          # generate simple, short, coverage stats
+
+rm -f /tmp/PID$$.*
+