From 4e4fb5021cc9aa67390f6641a060b85c077a1880 Mon Sep 17 00:00:00 2001 From: "E. Scott Daniels" Date: Tue, 24 Mar 2020 12:28:06 -0400 Subject: [PATCH] Add unit tests and changes related 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 Change-Id: Ibc593ddc8687ce6d727bf6d3e2939c02f1e0afef --- CMakeLists.txt | 11 +- Dockerfile | 100 ++++++++++++++++++ build_rmr.sh | 23 +++++ examples/xapp_t1.cpp | 6 +- rmr-version.yaml | 3 + src/messaging/callback.cpp | 14 +-- src/messaging/callback.hpp | 6 +- src/messaging/default_cb.cpp | 2 +- src/messaging/default_cb.hpp | 2 +- src/messaging/message.cpp | 79 ++++++++++----- src/messaging/message.hpp | 16 +-- src/messaging/messenger.cpp | 12 ++- src/messaging/messenger.hpp | 2 +- test/.gitignore | 4 + test/Makefile | 21 ++++ test/parse_gcov.sh | 180 +++++++++++++++++++++++++++++++++ test/rmr_em.c | 236 +++++++++++++++++++++++++++++++++++++++++++ test/scrub_gcov.sh | 52 ++++++++++ test/unit_test.cpp | 229 +++++++++++++++++++++++++++++++++++++++++ test/unit_test.sh | 88 ++++++++++++++++ 20 files changed, 1026 insertions(+), 60 deletions(-) create mode 100644 Dockerfile create mode 100755 build_rmr.sh create mode 100644 rmr-version.yaml create mode 100644 test/.gitignore create mode 100644 test/Makefile create mode 100755 test/parse_gcov.sh create mode 100644 test/rmr_em.c create mode 100755 test/scrub_gcov.sh create mode 100644 test/unit_test.cpp create mode 100755 test/unit_test.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index d702d45..9e59870 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 index 0000000..f9f9eab --- /dev/null +++ b/Dockerfile @@ -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 index 0000000..905a15d --- /dev/null +++ b/build_rmr.sh @@ -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 + diff --git a/examples/xapp_t1.cpp b/examples/xapp_t1.cpp index 8cce435..e525891 100644 --- a/examples/xapp_t1.cpp +++ b/examples/xapp_t1.cpp @@ -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 index 0000000..5dd30dc --- /dev/null +++ b/rmr-version.yaml @@ -0,0 +1,3 @@ +# Communicate to CI which version of RMR to install in the build/vet environment +--- +version: 3.6.2 diff --git a/src/messaging/callback.cpp b/src/messaging/callback.cpp index d7eacc6..d4d697d 100644 --- a/src/messaging/callback.cpp +++ b/src/messaging/callback.cpp @@ -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 ); } diff --git a/src/messaging/callback.hpp b/src/messaging/callback.hpp index 2b7813d..b8e9dfa 100644 --- a/src/messaging/callback.hpp +++ b/src/messaging/callback.hpp @@ -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 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 }; diff --git a/src/messaging/default_cb.cpp b/src/messaging/default_cb.cpp index d7cfdbf..1083293 100644 --- a/src/messaging/default_cb.cpp +++ b/src/messaging/default_cb.cpp @@ -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" ); diff --git a/src/messaging/default_cb.hpp b/src/messaging/default_cb.hpp index fdc084a..2e96003 100644 --- a/src/messaging/default_cb.hpp +++ b/src/messaging/default_cb.hpp @@ -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 diff --git a/src/messaging/message.cpp b/src/messaging/message.cpp index 0c6b262..d149cc0 100644 --- a/src/messaging/message.cpp +++ b/src/messaging/message.cpp @@ -97,28 +97,30 @@ std::unique_ptr Message::Copy_payload( ){ std::unique_ptr 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( 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 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 new_meid ) { +void Message::Set_meid( std::shared_ptr 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 response ) { +bool Message::Send_response( int mtype, int subid, int response_len, std::shared_ptr 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 response ) { - return Send( NOCHANGE, NOCHANGE, response_len, response.get(), RESPONSE ); +bool Message::Send_response( int response_len, std::shared_ptr 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 payload ) { +bool Message::Send_msg( int mtype, int subid, int payload_len, std::shared_ptr 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 payload ) { - return Send( NOCHANGE, NOCHANGE, payload_len, payload.get(), MESSAGE ); +bool Message::Send_msg( int payload_len, std::shared_ptr 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 ); } diff --git a/src/messaging/message.hpp b/src/messaging/message.hpp index 8c06ed1..ce654d5 100644 --- a/src/messaging/message.hpp +++ b/src/messaging/message.hpp @@ -53,13 +53,12 @@ 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 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 Copy_payload( ); // copy the payload; deletable smart pointer - std::unique_ptr Get_meid(); // returns a copy of the meid bytes + std::unique_ptr 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 new_meid ); + void Set_meid( std::shared_ptr 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 payload ); + bool Send_msg( int mtype, int subid, int payload_len, std::shared_ptr payload ); bool Send_msg( int mtype, int subid, int payload_len, unsigned char* payload ); - bool Send_msg( int payload_len, std::unique_ptr payload ); + bool Send_msg( int payload_len, std::shared_ptr payload ); bool Send_msg( int payload_len, unsigned char* payload ); - bool Send_response( int mtype, int subid, int payload_len, std::unique_ptr response ); + bool Send_response( int mtype, int subid, int payload_len, std::shared_ptr response ); bool Send_response( int mtype, int subid, int payload_len, unsigned char* response ); - bool Send_response( int payload_len, std::unique_ptr response ); + bool Send_response( int payload_len, std::shared_ptr response ); bool Send_response( int payload_len, unsigned char* response ); }; diff --git a/src/messaging/messenger.cpp b/src/messaging/messenger.cpp index d4033c3..6951f7d 100644 --- a/src/messaging/messenger.cpp +++ b/src/messaging/messenger.cpp @@ -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; } diff --git a/src/messaging/messenger.hpp b/src/messaging/messenger.hpp index 0fa7899..c8e4f13 100644 --- a/src/messaging/messenger.hpp +++ b/src/messaging/messenger.hpp @@ -71,7 +71,7 @@ class Messenger { void Listen( ); // lisen driver std::unique_ptr 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 index 0000000..9903b8d --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,4 @@ +core +*.gcov +*.gcda +*.gcno diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..fcbf361 --- /dev/null +++ b/test/Makefile @@ -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 index 0000000..d69a88b --- /dev/null +++ b/test/parse_gcov.sh @@ -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 index 0000000..581248e --- /dev/null +++ b/test/rmr_em.c @@ -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 +#include +#include + + +/* + 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 index 0000000..349fca2 --- /dev/null +++ b/test/scrub_gcov.sh @@ -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 index 0000000..7ba91d5 --- /dev/null +++ b/test/unit_test.cpp @@ -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 + + +#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, " cbd: bad message type: %d\n", mtype ); + } + err_cbd++; + } else { + good_cbd++; + } +} + +void killer( std::shared_ptr 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 msg; + std::shared_ptr x; + Msg_component payload; + std::unique_ptr ucs; + unsigned char* new_payload; + std::shared_ptr 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( 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, " expected pointer to copy payload but got nil\n" ); + errors++; + } + + ucs = msg->Get_meid(); + if( ucs == NULL ) { + fprintf( stderr, " expected pointer to meid copy but got nil\n" ); + errors++; + } + + ucs = msg->Get_src(); + if( ucs == NULL ) { + fprintf( stderr, " expected pointer to src copy but got nil\n" ); + errors++; + } + + i = msg->Get_available_size(); + if( i != 2048 ) { + fprintf( stderr, " len expected payload avail size of 2048 but got %d\n", i ); + errors++; + } + + i = msg->Get_mtype(); + if( i != 100 ) { + fprintf( stderr, " expected mtype of 100 but got %d\n", i ); + errors++; + } + + i = msg->Get_state( ); + if( i != 0 ) { + fprintf( stderr, " expected state of 0 but got %d\n", i ); + errors++; + } + + i = msg->Get_subid(); + if( i != -10 ) { + fprintf( stderr, " expected subid of -10 but got %d\n", i ); + errors++; + } + + i = msg->Get_len(); + if( i != 128 ) { + fprintf( stderr, " 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( 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, " 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, " one or more callbacks reported an error: [%d] [%d] [%d]\n", err_cb1, err_cb2, err_cbd ); + fprintf( stderr, " 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 index 0000000..9e0564b --- /dev/null +++ b/test/unit_test.sh @@ -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$$.* + -- 2.16.6