Add full unit tests for listener 91/1991/4
authorE. Scott Daniels <daniels@research.att.com>
Tue, 10 Dec 2019 22:06:25 +0000 (17:06 -0500)
committerE. Scott Daniels <daniels@research.att.com>
Wed, 11 Dec 2019 14:09:37 +0000 (09:09 -0500)
This change adds unit tests for the two listener library
modules, and the scripts to allow these tests to be driven
during the image build as a verification. In addition the
coverage stats for the tests are reported.

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

sidecars/listener/.gitignore
sidecars/listener/Dockerfile
sidecars/listener/Makefile
sidecars/listener/discount_chk.ksh [new file with mode: 0755]
sidecars/listener/mcl.c
sidecars/listener/rdc.c
sidecars/listener/run_unit_test.ksh [new file with mode: 0755]
sidecars/listener/show_coverage.ksh [new file with mode: 0755]
sidecars/listener/test_rmr_em.c [new file with mode: 0644]
sidecars/listener/unit_test.c

index 720aacc..0225339 100644 (file)
@@ -1,3 +1,6 @@
 *.o
 *.a
-
+*.gcov
+*.dcov
+*.gcno
+*.gcda
index 31c986f..de8755b 100644 (file)
@@ -57,11 +57,22 @@ RUN dpkg -i rmr-dev_${RMR_VER}_amd64.deb
 
 RUN mkdir /playpen/bin /playpen/src
 ARG SRC=.
-COPY ${SRC}/Makefile ${SRC}/*.h ${SRC}/*.c /playpen/src/
+COPY ${SRC}/*.ksh ${SRC}/Makefile ${SRC}/*.h ${SRC}/*.c /playpen/src/
+COPY ${SRC}/verify_replay.sh ${SRC}/verify.sh /playpen/src/
 
+# Build all binaries; verify scripts expect them to be in bin, so we must copy too
+#
 ENV LD_LIBRARY_PATH=/usr/local/lib64:/usr/local/lib
 ENV C_INCLUDE_PATH=/usr/local/include
-RUN cd /playpen/src/; make -B mc_listener sender pipe_reader rdc_replay rdc_extract
+RUN cd /playpen/src/; make -B all; ls -al mc_listener; cp mc_listener sender pipe_reader rdc_replay rdc_extract /playpen/bin/
+
+# Run unit tests. If they don't pass the build fails here. Tests can be run from src, but expect binaries in bin
+# so that they can be run in the final image as well.
+#
+ENV PATH /playpen/bin:/playpen/src:$PATH
+RUN cd /playpen/src/; ./run_unit_test.ksh
+RUN /playpen/src/verify.sh; /playpen/src/verify_replay.sh
+
 
 # -----  final, smaller image ----------------------------------
 FROM ubuntu:18.04
@@ -76,7 +87,7 @@ ARG SRC
 RUN mkdir -p /playpen/bin
 COPY --from=buildenv /usr/local/lib/* /usr/local/lib/
 COPY --from=buildenv /playpen/src/mc_listener /playpen/src/sender /playpen/src/pipe_reader /playpen/src/rdc_replay /playpen/src/rdc_extract /playpen/bin/
-COPY ${SRC}/verify_replay.sh ${SRC}/verify.sh ${SRC}run_replay.sh ${SRC}/help /playpen/bin/
+COPY ${SRC}/verify_replay.sh ${SRC}/verify.sh ${SRC}/run_replay.sh ${SRC}/help /playpen/bin/
 
 ENV PATH=/playpen/bin:$PATH
 ENV LD_LIBRARY_PATH=/usr/local/lib64:/usr/local/lib
index a9e6dc9..117c2bd 100644 (file)
 # LD_LIBRARY_PATH, LIBRARY_PATH are set correctly.
 
 binaries = mc_listener
+adjuncts = rdc_replay rdc_extract
+testers = sender pipe_reader
+
 test_progs = sender unit_test pipe_reader
 lib_obj = mcl.o rdc.o
 lib_h = mcl.h
 
 coverage_opts = -ftest-coverage -fprofile-arcs
 
-# make with no parms should just build 'production' binaries
-all: $(binaries) mc_listener
+# make with no parms should build all production and adjunct/verification binaries
+all: $(binaries) $(adjuncts) $(testers)
 
 libmcl.a::     $(lib_obj) $(lib_h)
        ar -v -r libmcl.a $(lib_obj)
@@ -47,14 +50,14 @@ pipe_reader : pipe_reader.c libmcl.a
        gcc pipe_reader.c -o pipe_reader  -L. -lmcl -lrmr_nng -lnng -lm -lpthread
 
 unit_test: unit_test.c mcl.c
-       gcc $(coverage_opts) unit_test.c -o unit_test -lrmr_nng -lnng -lm -lpthread
+       gcc -g $(coverage_opts) unit_test.c -o unit_test -lrmr_nng -lnng -lm -lpthread
 
 # ---- adjunct tools -----------------------------------------------------------------
 rdc_replay: rdc_replay.c libmcl.a
-       gcc $(coverage_opts) rdc_replay.c -o rdc_replay -L. -lmcl -lrmr_nng -lnng -lpthread -lm 
+       gcc rdc_replay.c -o rdc_replay -L. -lmcl -lrmr_nng -lnng -lpthread -lm 
 
 rdc_extract: rdc_extract.c libmcl.a
-       gcc $(coverage_opts) rdc_extract.c -o rdc_extract -L. -lmcl -lrmr_nng -lnng -lpthread -lm 
+       gcc rdc_extract.c -o rdc_extract -L. -lmcl -lrmr_nng -lnng -lpthread -lm 
 
 # ---- housekeeping stuff -------------------------------------------------------------
 # remove only intermediates
diff --git a/sidecars/listener/discount_chk.ksh b/sidecars/listener/discount_chk.ksh
new file mode 100755 (executable)
index 0000000..e172452
--- /dev/null
@@ -0,0 +1,197 @@
+#!/usr/bin/env bash
+
+#==================================================================================
+#        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:       discount_chk.ksh
+#      Abstract:       This checks the gcov output from unit tests to determine
+#                              what number of lines of code are highly untestable. Primararily
+#                              these are lines in blocks which are executed when systems
+#                              calles (e.g. malloc) fail.
+#
+#      Date:           10 December 2019
+#      Author:         E. Scott Daniels
+# -------------------------------------------------------------------------
+
+
+#
+#      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 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_gcov {
+       typeset f="$1"
+
+       #mct=$( get_mct ${1%.gcov} )                    # see if a special coverage target is defined for this
+       mct=80
+
+       if [[ ! -f $1 ]]
+       then
+               if [[ -f ${1##*/} ]]
+               then
+                       f=${1##*/}
+               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 ) {
+                                       if( replace_flags == 1 ) {
+                                               gsub( "#####", "    1", $0 )
+                                       } else {
+                                               gsub( "#####", "=====", $0 )
+                                       }
+                               }
+                               discount++;
+                       }
+               }
+
+               seq++;;
+               spit_line()
+               next;
+       }
+
+       /if[(].*errno ==.*{/ ||                 # if( errno == ... ) assume error check
+
+       /if[(].*alloc.*{/ {                     # if( (x = malloc( ... )) != NULL ) or if( (p = sym_alloc(...)) != NULL )
+               prev_malloc = 1
+               prev_if = 1
+               spit_line()
+               seq = $2+0
+               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
+}
+
+# ----------------------------------------------------------------
+
+ignore_list="main"
+module_cov_target=80
+chatty=0
+show_all=1
+replace_flags=1
+cfail="FAIL"
+
+while [[ $1 == -* ]]
+do
+       case $1 in 
+               -a)     show_all-1;;                                    # only show things not passing
+               -c) module_cov_target=$2;;                      # coverage target for module
+               -r)     replace_flag=$2; shift;;                # replace with ==== if 2; no repalce if 0
+               -V)     chatty=1;;                                              # show "dcov" output
+       esac
+
+       shift
+done
+
+for ckf in "$@"
+do
+       discount_gcov ${ckf%.gcov}.gcov
+done
index 262486b..764be7e 100644 (file)
 
 #include "mcl.h"
 
+#ifndef FOREVER
+#define FOREVER 1
+#endif
+
 #define READER 0
 #define WRITER 1
 
@@ -83,7 +87,7 @@ typedef struct {
 /*
        Set up for raw data capture. We look for directory overriedes from
        environment variables, and then invoke the rdc_init() to actually
-       set things upd.
+       set things up.
 */
 static void* setup_rdc() {
        void*   ctx;
@@ -183,7 +187,7 @@ static char* build_hdr( int len, char* dest, int dest_len ) {
 */
 static int open_fifo( mcl_ctx_t* ctx, int mtype, int io_dir ) {
        char    wbuf[1024];
-       int             fd;                             // real file des
+       int             fd;                                     // real file des
        int             jfd = -1;                       // junk file des
        int             state;
 
@@ -234,10 +238,17 @@ static int open_fifo( mcl_ctx_t* ctx, int mtype, int io_dir ) {
        allowing for direct update of counts after the write.
 */
 static int suss_fifo( mcl_ctx_t* ctx, int mtype, int io_dir, fifo_t** fref ) {
-       fifo_t* fifo;
+       fifo_t* fifo = NULL;
        void*   hash;
 
-       if( io_dir == READER ) {                // with an integer key, we nned two hash tables
+       if( ctx == NULL ) {
+               if( fref != NULL ) {
+                       *fref = NULL;
+               }
+               return -1;
+       }
+
+       if( io_dir == READER ) {                // with an integer key, we need two hash tables
                hash = ctx->rd_hash;
        } else {
                hash = ctx->wr_hash;
@@ -245,20 +256,24 @@ static int suss_fifo( mcl_ctx_t* ctx, int mtype, int io_dir, fifo_t** fref ) {
 
        if( (fifo = (fifo_t *) rmr_sym_pull( hash, mtype )) == NULL ) {
                fifo = (fifo_t *) malloc( sizeof( *fifo ) );
-               if( fifo == NULL ) {
-                       return -1;
+               if( fifo != NULL ) {
+                       memset( fifo, 0, sizeof( *fifo ) );
+                       fifo->key = mtype;
+                       fifo->fd = open_fifo( ctx, mtype, io_dir );
+                       if( fifo->fd >= 0 ) {                                   // save only on good open
+                               rmr_sym_map( hash, mtype, fifo );
+                       } else {
+                               free( fifo );
+                               fifo = NULL;
+                       }
                }
-
-               memset( fifo, 0, sizeof( *fifo ) );
-               fifo->key = mtype;
-               fifo->fd = open_fifo( ctx, mtype, io_dir );
-               rmr_sym_map( hash, mtype, fifo );
        }
 
        if( fref != NULL ) {
                *fref = fifo;
        }
-       return fifo->fd;
+
+       return fifo == NULL ? -1 : fifo->fd;
 }
 
 /*
@@ -582,7 +597,7 @@ extern void mcl_fifo_fanout( void* vctx, int report, int long_hdr  ) {
 
        rdc_ctx = setup_rdc( );                         // pull rdc directories from enviornment and initialise
 
-       while( 1 ) {
+       do {
                mbuf = mcl_get_msg( ctx, mbuf, report );                        // wait up to report sec for msg (0 == block until message)
 
                if( mbuf != NULL && mbuf->state == RMR_OK && mbuf->len > 0  ) {
@@ -632,7 +647,7 @@ extern void mcl_fifo_fanout( void* vctx, int report, int long_hdr  ) {
                                fflush( stdout );
                        }
                }
-       }
+       } while( FOREVER );                             // forever allows for escape during unit testing
 }
 
 
index bf7300e..8edf8c5 100644 (file)
@@ -86,7 +86,7 @@ typedef struct {
 
        There seems to be an issue with some collectors and thus it is required 
        to initially name the file with a leading dot (.) until the file is closed
-       and ready to be read by external processes (marking it write only seems
+       and ready to be read by external processes (marking it write-only seems
        not to discourage them from trying!).
 */
 static int copy_unlink( char* old, char* new, int mode ) {
@@ -104,7 +104,7 @@ static int copy_unlink( char* old, char* new, int mode ) {
        
        errno = 0;
        if( (rfd = open( old, O_RDONLY )) < 0 ) {
-               logit( LOG_ERR, "copy: open src for copy failed: %s: %s\n", old, strerror( errno ) );
+               logit( LOG_ERR, "copy: open src for copy failed: %s: %s", old, strerror( errno ) );
                return -1;
        }
 
@@ -122,7 +122,7 @@ static int copy_unlink( char* old, char* new, int mode ) {
        //logit( LOG_INFO, "copy: creating file in tmp filename: %s", tfname );
 
        if( (wfd = open( tfname, O_WRONLY | O_CREAT | O_TRUNC, 0200 )) < 0 ) {
-               logit( LOG_ERR, "copy: open tmp file for copy failed: %s: %s\n", tfname, strerror( errno ) );
+               logit( LOG_ERR, "copy: open tmp file for copy failed: %s: %s", tfname, strerror( errno ) );
                return -1;
        }
 
@@ -346,11 +346,12 @@ extern void rdc_set_freq( void* vctx, int freq ) {
 
        ctx = (rdc_ctx_t *) vctx;
        
-       if( ctx != NULL && freq > 10 ) {
+       if( ctx != NULL && freq >= 10 ) {
                ctx->frequency = freq;
+               logit( LOG_INFO, "(rdc) file roll frequency set to %d seconds", ctx->frequency );
+       } else {
+               logit( LOG_ERR, "(rdc) file roll frequency was not set; ctx was nill or frequency was less than 10s" );
        }
-
-       logit( LOG_INFO, "(rdc) file roll frequency set to %d seconds", ctx->frequency );
 }
 
 /*
@@ -471,7 +472,7 @@ int main( ) {
        ctx = rdc_init( "/tmp/rdc/stage", "/tmp/rdc/final", ".rdc", NULL );                     // does not create done files
        //ctx = rdc_init( "/tmp/rdc/stage", "/tmp/rdc/final", ".rdc", ".done" );        // will create a "done" file
        if( ctx == NULL ) {
-               logit( LOG_CRIT, "<TEST> abort! rdc_init returned a nil pointer\n" );
+               logit( LOG_CRIT, "<TEST> abort! rdc_init returned a nil pointer" );
                exit( 1 );
        }
 
diff --git a/sidecars/listener/run_unit_test.ksh b/sidecars/listener/run_unit_test.ksh
new file mode 100755 (executable)
index 0000000..e20f738
--- /dev/null
@@ -0,0 +1,122 @@
+#!/usr/bin/env bash
+
+#==================================================================================
+#        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:       run_unit_test.ksh
+#      Abstract:       This drives unit testing setting up working directories
+#                              and such.
+#
+#      Date:           10 December 2019
+#      Author:         E. Scott Daniels
+# -------------------------------------------------------------------------
+
+function setup_dirs {
+       mkdir -p /tmp/fifos
+       mkdir -p /tmp/mc_listener_test/final
+       mkdir -p /tmp/mc_listener_test/stage
+
+       mv_src=/tmp/mc_listener_test/mv_src             # source that will be renamed rather than copied
+       mv_dest=/tmp/mc_listener_test/mv_dest
+       ps -elf >$mv_src
+
+       copy_src=/tmp/mc_listener_test/copy_src
+       copy_dest=/tmp/mc_listener_test/copy_dest
+       ps -elf >$copy_src
+
+       src_md5=$( cat $copy_src | md5sum )             # use cat so that filename doesn't factor in to output
+       rm -f $copy_dest
+}
+
+function purge_dirs {
+       rm -fr /tmp/mc_listener_test
+}
+
+
+if ! make -B unit_test                 # ensure that it's fresh
+then
+       echo "[FAIL] cannot make unit_test"
+       exit 1
+fi
+
+if [[ $1 == "purge" ]]
+then
+       purge_dirs
+       exit 0
+fi
+
+setup_dirs
+
+if [[ $1 == "set"* ]]
+then
+       exit
+fi
+
+if ! unit_test >/tmp/PID$$.utlog 2>&1
+then
+       cat /tmp/PID$$.utlog
+       rm -f /tmp/PID$$.*
+       purge_dirs
+       exit 1
+fi
+
+echo "[PASS] base unit tests all pass"
+echo "[INFO] file/directory verification begins...."
+
+# validate files that should have been created/copied
+
+rc=0
+
+ls -al /tmp/mc_listener_test/* >/tmp/PID$$.fdlog 2>&1
+
+if [[ -e $copy_src ]]
+then
+       echo "[FAIL] copy source test should have been unlinked but was there!"
+       rc=1
+else 
+       dest_md5=$( cat $copy_dest | md5sum )           # use cat so that filename doesn't factor in to output
+       if [[ $dest_md5 != $src_md5 ]]
+       then
+               echo "[FAIL] md5 of copy test file didn't match soruce"
+cat $dest_md5
+echo "$dest_md5  $src_md5"
+               rc=1
+       fi
+fi
+purge_dirs
+
+if (( rc > 0 ))
+then
+       cat /tmp/PID$$.fdlog
+fi
+
+
+show_coverage.ksh unit_test.c                                                          # compute coverage and generate .gcov files
+echo "Coverage with discounting (raw values in parens)"
+discount_chk.ksh $(ls *gcov|egrep -v "^test_|unit_test.c")
+
+
+if (( rc > 0 ))
+then
+       echo "[FAIL] overall test fails"
+else
+       echo "[PASS] overall test passes"
+fi
+
+rm -f /tmp/PID$$.*
+exit $rc
diff --git a/sidecars/listener/show_coverage.ksh b/sidecars/listener/show_coverage.ksh
new file mode 100755 (executable)
index 0000000..98d6d49
--- /dev/null
@@ -0,0 +1,137 @@
+#!/usr/bin/env bash
+
+#==================================================================================
+#        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:       show_coverage.ksh
+#      Abstract:       This script parses the coverage file associated with the .c file
+#                              listed on the command line and displays a more readable summary.
+#
+#      Date:           10 December 2019
+#      Author:         E. Scott Daniels
+# -------------------------------------------------------------------------
+
+show_all=0
+ignore_list="main"
+module_cov_target=80
+chatty=0
+show_all=0
+cfail="DCHK"
+
+gcov -f $1 | 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
+                       }
+
+                       exit_code = 0           # assume good
+               }
+
+               /^TARGET:/ {
+                       if( NF > 1 ) {
+                               target[$2] = $NF
+                       }
+                       next;
+               }
+
+               /File.*_test/ || /File.*test_/ {                # dont report on test files
+                       skip = 1
+                       file = 1
+                       fname = $2
+                       next
+               }
+
+               /File/ {
+                       skip = 0
+                       file = 1
+                       fname = $2
+                       next
+               }
+
+               /Function/ {
+                       fname = $2
+                       file = 0
+                       if( imap[fname] ) {
+                               fname = "skipped: " fname               # should never see and make it smell if we do
+                               skip = 1
+                       } else {
+                               skip = 0
+                       }
+                       next
+               }
+
+               skip { next }
+
+               /Lines executed/ {
+                       split( $0, a, ":" )
+                       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( announce_target ) {                                 # annoucne for module if different from default
+                                       printf( "[INFO] target coverage for %s is %d%%\n", fname, mct )
+                               }
+
+                               if( pct < mct ) {
+                                       printf( "[%s] %3d%% %s\n", cfail, pct, fname )  # CAUTION: write only 3 things  here
+                                       exit_code = 1
+                               } else {
+                                       printf( "[PASS] %3d%% %s\n", pct, fname )
+                               }
+
+                               announce_target = 0;
+                       } else {
+                               if( pct < 70 ) {
+                                       printf( "[LOW]  %3d%% %s\n", pct, fname )
+                               } else {
+                                       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 )
+               }
+       '
diff --git a/sidecars/listener/test_rmr_em.c b/sidecars/listener/test_rmr_em.c
new file mode 100644 (file)
index 0000000..5034b77
--- /dev/null
@@ -0,0 +1,60 @@
+// 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:       test_rmr_em.c.
+       Abstract:       Emulates some RMR functions (message receipt mostly)
+                               so that we can unit test.
+
+                               This file should be included by the test programme BEFORE
+                               any module which uses RMR functions is included
+
+       Date:           10 December 2019
+       Author:         E. Scott Daniels
+*/
+
+#include <rmr/rmr.h>
+
+#define rmr_torcv_msg RMR_torcv_msg
+
+extern rmr_mbuf_t* RMR_torcv_msg( void* ctx, rmr_mbuf_t* old_msg, int ms_to ) {
+       static int timeout = 0;
+
+       if( old_msg == NULL ) {
+               old_msg = rmr_alloc_msg( ctx, 256 );
+               if( old_msg == NULL ) {
+                       return NULL;
+               }
+       }
+
+       timeout = ! timeout;                                    // every other message results in a timeout
+       if( timeout ) {
+               old_msg->state = RMR_ERR_TIMEOUT;
+               return old_msg; 
+       }
+
+
+       snprintf( old_msg->payload, 100, "DUMMY MESSAGE" );
+       old_msg->mtype = 100;
+       old_msg->len = strlen( old_msg->payload );
+       old_msg->sub_id = -1;
+       old_msg->state = 0;
+
+       return old_msg;
+}
index 79bc43f..9699f6c 100644 (file)
        Author:         E. Scott Daniels
 */
 
-// this/these are what we are testing; include them directly
+#define FOREVER 0                      // allows forever loops in mcl code to escape after one loop
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "test_rmr_em.c"               // emulated rmr functions (for receives)
+
+// this/these are what we are testing; include them directly (must be after forever def)
 #include "mcl.c"
+#include "rdc.c"
+
+/*
+       Set up env things for the rdc setup call.
+*/
+static void set_env() {
+       setenv( "MCL_RDC_ENABLE", "1", 1 );                                                                     // cause 'raw' capture to be setup
+       setenv( "MCL_RDC_STAGE", "/tmp/mc_listener_test/stage", 1 );            // unit test script should create and will purge after
+       setenv( "MCL_RDC_FINAL", "/tmp/mc_listener_test/final", 1 );
+       setenv( "MCL_RDC_SUFFIX", ".xxx", 1 );
+       setenv( "MCL_RDC_DONE", ".done", 1 );
+       setenv( "MCL_RDC_FREQ", "10", 1 );
+}
 
 /*
        Parms:  [fifo-dir-name]
 */
 int main( int argc,  char** argv ) {
        void*   ctx;
-       int             errors;
+       void*   bad_ctx;                                // context with a bad directory path for coverage/error testing
+       int             errors = 0;
        char*   dname = "/tmp/fifos";
        char*   port = "4560";
        int             fd;
        int             fd2;
+       int             rfd;                                    // a reader file des so we can read what we write
+       fifo_t* fref = NULL;                    // fifo type reference; we just verify that suss sets it
+       char    wbuf[2048];
+       char    payload[1024];
+       char*   bp;
+       void*   buf;
+       int state;
+       char    timestamp[1024];                // read will put a timestamp here
 
        if( argc > 1 ) {
                dname = argv[1];
        }
        
-       ctx = mcl_mk_context( dname );
+       set_env();                                                      // set env that setup_rdc() looks for
+
+       ctx = mcl_mk_context( dname );                  // allocate the context
        if( ctx == NULL ) {
-               fprintf( stderr, "[FAIL] couldn't make context" );
+               fprintf( stderr, "[FAIL] couldn't make context\n" );
                exit( 1 );
        }
 
-       mcl_set_sigh();                 // prevent colobber from broken pipe
+       mcl_set_sigh();                                                                         // prevent colobber from broken pipe
 
-       fd = suss_fifo( ctx, 101, 1 );                          // should open the file for writing and return the fdes
+       open_fifo( ctx, 100, WRITER );                                          // open dummy to prevent blocking reader
+       rfd = open_fifo( ctx, 100, READER );                            // open a reader to check fanout output
+       if( rfd < 0 ) {
+               fprintf( stderr, "[FAIL] unable to open a pipe reader for type == 100\n" );
+               errors++;
+       }
+
+       fd = suss_fifo( ctx, 100, 1, &fref );                           // should open the file for writing and return the fdes
        if( fd < 0 ) {
                fprintf( stderr, "[FAIL] suss_fifo did not return a valid fd\n" );
                errors++;
        }
 
-       fd2= suss_fifo( ctx, 101, 0 );                          // should open the file file for reading and return a different fd
+       if( fref == NULL ) {
+               fprintf( stderr, "[FAIL] suss_fifo did not set the fifo reference pointer\n" );
+               errors++;
+       } else {
+               chalk_ok( fref );
+               chalk_error( fref );
+       }
+
+       fd2= suss_fifo( ctx, 100, 0, NULL );                            // should open the file file for reading and return a different fd
        if( fd < 0 ) {
                fprintf( stderr, "[FAIL] suss_fifo did not return a valid fd\n" );
                errors++;
@@ -66,10 +120,161 @@ int main( int argc,  char** argv ) {
                errors++;
        }
 
-       mcl_start_listening( ctx, port, 0 );                    // start the listener, no waiting for rt since we don't send
+       mcl_start_listening( ctx, port, 0 );                    // start the listener
+
+                                                                                                       // under test, the forever keeps fanout from blocking; drive for each of two cases:
+       mcl_fifo_fanout( ctx, 5, 1 );                                   // first rmr receive call will simulate a timeout
+       mcl_fifo_fanout( ctx, 5, 1 );                                   // second receive call simualtes a message arriving
+       mcl_fifo_fanout( ctx, 5, 1 );                                   // another round so there are two to read
+       mcl_fifo_fanout( ctx, 5, 1 );
+
+       *timestamp = 0;
+       state = mcl_fifo_read1( ctx, 100, payload, sizeof( payload ), TRUE );
+       if( state < 1 ) {
+               fprintf( stderr, "[FAIL] fifo_read return positive value when expected to\n" );
+               errors++;
+       }
+       state = mcl_fifo_tsread1( ctx, 100, payload, sizeof( payload ), TRUE, timestamp );
+       if( state < 1 ) {
+               fprintf( stderr, "[FAIL] fifo_read with timestamp return positive value when expected to\n" );
+               errors++;
+       }
+
+       state = fifo_read1( NULL, 100, payload, sizeof( payload ), 1, timestamp );              // coverage error check
+       if( state != 0 ) {
+               fprintf( stderr, "[FAIL] fifo_read didn't return 0 when given a nil context to\n" );
+               errors++;
+       }
+
+       mcl_fifo_fanout( ctx, 5, 0 );                                   // test with writing short header
+       mcl_fifo_fanout( ctx, 5, 0 );
+
+       // ------ some error/coverage testing ---------------------------
+       logit( LOG_CRIT, "critical message" );
+       logit( LOG_ERR, "error message" );
+       logit( LOG_WARN, "warning message" );
+       logit( LOG_STAT, "stats message" );
+
+       bad_ctx = mcl_mk_context( "/nosuchdirectoryinthesystem" );              // create a context where fifo opens should fail
+       if( bad_ctx == NULL ) {
+               fprintf( stderr, "[FAIL] couldn't make 'bad' context" );
+               exit( 1 );
+       }
+
+       fref = NULL;
+       fd = suss_fifo( bad_ctx, 100, 1, &fref );                               // should fail to open the file for writing beacuse directory is bad
+       if( fd >= 0 ) {
+               fprintf( stderr, "[FAIL] suss_fifo returned a valid fd when given a context with a bad directory path\n" );
+               errors++;
+       }
+       if( fref != NULL ) {
+               fprintf( stderr, "[FAIL] suss_fifo returned an fref pointer when given a bad context\n" );
+               errors++;
+       }
+
+       fd = suss_fifo( NULL, 100, 1, &fref );                          // coverage nil pointer check
+       if( fd >= 0 ) {
+               fprintf( stderr, "[FAIL] suss_fifo returned a valid fd when given a nil context a bad directory path\n" );
+               errors++;
+       }
+
+       fd = suss_fifo( ctx, -1, 1, &fref );                            // mad message type check
+       if( fd >= 0 ) {
+               fprintf( stderr, "[FAIL] suss_fifo returned a valid fd when given a bad message type\n" );
+               errors++;
+       }
+
+       // -- buffer testing ------------------------------------------------------
+       bp = build_hdr( 1024, wbuf, 0 );
+       bp = build_hdr( 1024, NULL, 0 );
+       if( bp == NULL ) {
+               fprintf( stderr, "[FAIL] build_hdr didn't return a buffer pointer when given a nil buffer\n" );
+               errors++;
+       }
+       free( bp );
+
+       bp = build_hdr( 1024, wbuf, sizeof( wbuf ) );
+       if( bp == NULL ) {
+               fprintf( stderr, "[FAIL] build_hdr didn't return a buffer pointer\n" );
+               errors++;
+       }
+
+
+       // ----- msg receive testing ----------------------------------------------------
+       buf = mcl_get_msg( NULL, NULL, 1 );                     // drive nil pointer checks
+       if( buf != NULL ) {
+               errors++;
+               fprintf( stderr, "[FAIL], get_msg call with nil context returned a buffer pointer\n" );
+       }
+
+       buf = mcl_get_msg( ctx, NULL, 1 );                      // drive to force coverage; nothing is sent, so we can't validate buffer
+
+
+       mcl_fifo_one( NULL, NULL, 1, 1 );
+       mcl_fifo_one( ctx, NULL, 1, 1 );
+       mcl_fifo_one( ctx, wbuf, 0, 1 );
+       mcl_fifo_one( ctx, wbuf, 10, 100 );
+
+
+       // --- some rdc testing as best as we can without message generators --------
+       rdc_init( NULL, NULL, ".foo", ".bar" ); // coverage testing
+
+       ctx = setup_rdc();                                              // coverage test to ensure that it generates a context
+       if( ctx == NULL ) {
+               fprintf( stderr, "[FAIL] setup_rdc did not return a context pointer\n" );
+               errors++;
+       }
+
+       rdc_set_freq( NULL, 0 );                                        // error/nil test
+       rdc_set_freq( ctx, 0 );                                 // error/nil test
+       rdc_set_freq( ctx, 10 );                                // roll after 10seconds to test that
+
+       build_hdr( 1024, wbuf, sizeof( wbuf ) );
+       bp = NULL;
+       bp = rdc_init_buf( 100, wbuf, 10, bp );                                 // set up for write
+       rdc_write( ctx, bp, payload, sizeof( payload ) );                               // write the raw data
+
+       fprintf( stderr, "[INFO] pausing to test rdc file rolling\n" );
+       sleep( 15 );
+       build_hdr( 1024, wbuf, sizeof( wbuf ) );
+       bp = NULL;
+       bp = rdc_init_buf( 100, wbuf, 10, bp );
+       rdc_write( ctx, bp, payload, sizeof( payload ) );
+
+
+       // CAUTION:  filenames need to match those expected in the run script as it creates src, and will validate, destination files
+       state = copy_unlink( "/tmp/mc_listener_test/no-such-copy_src", "/tmp/mc_listener_test/copy_dest", 0664 );  // first couple drive for error and coverage
+       if( state >= 0 ) {
+               fprintf( stderr, "[FAIL] copy-unlink of bad file didn't return bad state\n" );
+               errors++;
+       }
+       state = copy_unlink( "/tmp/mc_listener_test/copy_src", "/tmp/mc_listener_test-nodir/copy_dest", 0664 );
+       if( state >= 0 ) {
+               fprintf( stderr, "[FAIL] copy-unlink of bad target didn't return bad state\n" );
+               errors++;
+       }
+       state = copy_unlink( "/tmp/mc_listener_test/copy_src", "/tmp/mc_listener_test/copy_dest", 0664 );  // drive for coverage; setup script can check contents
+       if( state < 0 ) {
+               fprintf( stderr, "[FAIL] copy-unlink expected success but failed\n" );
+               errors++;
+       }
+       state = mvocp( "/tmp/mc_listener_test/bad-src-mv_src", "/tmp/mc_listener_test/mv_dest" );
+       if( state >= 0 ) {
+               fprintf( stderr, "[FAIL] mv or copy expected failure didn't set bad state\n" );
+               errors++;
+       }
+       state = mvocp( "/tmp/mc_listener_test/mv_src", "/tmp/mc_listener_test/mv_dest" );
+       if( state < 0 ) {
+               fprintf( stderr, "[FAIL] mv or copy expected to succeed  didn't set good state\n" );
+               errors++;
+       }
+
 
+       // ---- finally, check error count, write nice cheerful message and exit ----
        if( ! errors ) {
-               fprintf( stderr, "[PASS] all tests look peachy\n" );
+               fprintf( stderr, "[PASS] unit_test: everything looks peachy\n" );
+       } else {
+               fprintf( stderr, "[FAIL] unit_test: there were %d errors\n", errors );
        }
 
        return errors != 0;