From 6c6ac644fc38772b343347cda37c884af8415cfb Mon Sep 17 00:00:00 2001 From: "E. Scott Daniels" Date: Tue, 10 Dec 2019 17:06:25 -0500 Subject: [PATCH] Add full unit tests for listener 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 Change-Id: If52c3819ebf6b2f12d63e996d49c7e5c3e6ef9b5 --- sidecars/listener/.gitignore | 5 +- sidecars/listener/Dockerfile | 17 ++- sidecars/listener/Makefile | 13 ++- sidecars/listener/discount_chk.ksh | 197 +++++++++++++++++++++++++++++++ sidecars/listener/mcl.c | 43 ++++--- sidecars/listener/rdc.c | 15 +-- sidecars/listener/run_unit_test.ksh | 122 ++++++++++++++++++++ sidecars/listener/show_coverage.ksh | 137 ++++++++++++++++++++++ sidecars/listener/test_rmr_em.c | 60 ++++++++++ sidecars/listener/unit_test.c | 223 ++++++++++++++++++++++++++++++++++-- 10 files changed, 793 insertions(+), 39 deletions(-) create mode 100755 sidecars/listener/discount_chk.ksh create mode 100755 sidecars/listener/run_unit_test.ksh create mode 100755 sidecars/listener/show_coverage.ksh create mode 100644 sidecars/listener/test_rmr_em.c diff --git a/sidecars/listener/.gitignore b/sidecars/listener/.gitignore index 720aacc..0225339 100644 --- a/sidecars/listener/.gitignore +++ b/sidecars/listener/.gitignore @@ -1,3 +1,6 @@ *.o *.a - +*.gcov +*.dcov +*.gcno +*.gcda diff --git a/sidecars/listener/Dockerfile b/sidecars/listener/Dockerfile index 31c986f..de8755b 100644 --- a/sidecars/listener/Dockerfile +++ b/sidecars/listener/Dockerfile @@ -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 diff --git a/sidecars/listener/Makefile b/sidecars/listener/Makefile index a9e6dc9..117c2bd 100644 --- a/sidecars/listener/Makefile +++ b/sidecars/listener/Makefile @@ -21,14 +21,17 @@ # 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 index 0000000..e172452 --- /dev/null +++ b/sidecars/listener/discount_chk.ksh @@ -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 diff --git a/sidecars/listener/mcl.c b/sidecars/listener/mcl.c index 262486b..764be7e 100644 --- a/sidecars/listener/mcl.c +++ b/sidecars/listener/mcl.c @@ -44,6 +44,10 @@ #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 } diff --git a/sidecars/listener/rdc.c b/sidecars/listener/rdc.c index bf7300e..8edf8c5 100644 --- a/sidecars/listener/rdc.c +++ b/sidecars/listener/rdc.c @@ -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, " abort! rdc_init returned a nil pointer\n" ); + logit( LOG_CRIT, " 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 index 0000000..e20f738 --- /dev/null +++ b/sidecars/listener/run_unit_test.ksh @@ -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 index 0000000..98d6d49 --- /dev/null +++ b/sidecars/listener/show_coverage.ksh @@ -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 index 0000000..5034b77 --- /dev/null +++ b/sidecars/listener/test_rmr_em.c @@ -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 + +#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; +} diff --git a/sidecars/listener/unit_test.c b/sidecars/listener/unit_test.c index 79bc43f..9699f6c 100644 --- a/sidecars/listener/unit_test.c +++ b/sidecars/listener/unit_test.c @@ -24,39 +24,93 @@ 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; -- 2.16.6