// :vi sw=4 ts=4 noet: /* ================================================================================== Copyright (c) 2020-2021 Nokia Copyright (c) 2020-2021 AT&T Intellectual Property. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================================================== */ /* Mmemonic: si95_test.c Abstract: This is the main driver to test the si95 core functions (within rmr/src/si/src/si95). Author: E. Scott Daniels Date: 6 March 2018 */ #include #include #include #include #include #include #include #include #include #include #include #include #include // these four needed for si address tests #include #include #include #define DEBUG 1 // specific test tools in this directory #undef NNG_UNDER_TEST // NNG is NOT under test so undefine if set #define NO_EMULATION 1 // no emulation of transport functions #define NO_PRIVATE_HEADERS 1 // no rmr_si or rmr_nng headers #define NO_DUMMY_RMR 1 // no msg things #include "test_support.c" // things like fail_if() #include "test_transport_em.c" // system/transport emulation (open, close, connect, etc) #include #include // ------------- dummy functions to force edge cases when we can --------------------------------------- #define SYSTEM_UNDER_TEST 1 // for conditional code /* These are global so they can be reset for individual tests. */ static int good_mallocs = 0; // number of initial good malocs before failurs static int bad_mallocs = 1; // number of failed mallocs (consecutive) static void* test_malloc( size_t n ) { fprintf( stderr, ">>>> test malloc: %d %d\n", good_mallocs, bad_mallocs ); if( good_mallocs ) { good_mallocs--; return malloc( n ); } if( bad_mallocs ) { bad_mallocs--; errno = ENOMEM; return NULL; } return malloc( n ); } // ----------------------------------------------------------------------------------------------------- #include //#include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include #include #include #include #define malloc test_malloc #include #undef malloc // --------------------------------------------------------------------- void* si_ctx = NULL; // a global context might be useful // --------------------------------------------------------------------- /* Fake callback to register. */ static int test_cb( void* data ) { return 0; } /* Returns error for coverage testing of CB calls */ static int test_cb_err( void* data ) { return -1; } /* Memory allocation/free related tests */ static int memory( ) { int errors = 0; void* ptr; void* iptr; // ---- SInew ---------------- ptr = SInew( 100 ); // invalid block type should return nil errors += fail_not_nil( ptr, "memory: sinew did not return nil when given a valid struct type" ); SItrash( 100, NULL ); // drive trash for coverage iptr = SInew( IOQ_BLK ); errors += fail_if_nil( iptr, "memory: sinew returned nil when given ioq request" ); SItrash( IOQ_BLK, iptr ); ptr = SInew( TP_BLK ); errors += fail_if_nil( ptr, "memory: sinew returned nil when given tpblk request" ); if( ptr ) { iptr = SInew( IOQ_BLK ); ((struct tp_blk *)ptr)->squeue = iptr; SItrash( TP_BLK, ptr ); } ptr = SInew( GI_BLK ); errors += fail_if_nil( ptr, "memory: sinew returned nil when given giblk request" ); SItrash( GI_BLK, ptr ); // GI block cannot be trashed, ensure this (valgind will complain about a leak) free( ptr ); // we can free GI block only in tests fprintf( stderr, " memory module finished with %d errors\n", errors ); return errors; } /* Test initialisation related things */ static int init() { int errors = 0; si_ctx = SIinitialise( 0 ); errors += fail_if_nil( si_ctx, "init: siinit returned a nil pointer" ); SIclr_tflags( si_ctx, 0x00 ); // drive for coverage; no return value from these SIset_tflags( si_ctx, 0x03 ); fprintf( stderr, " init module finished with %d errors\n", errors ); return errors; } static int cleanup() { int errors = 0; if( ! si_ctx ) { fprintf( stderr, " cleanup has no context to use\n" ); return 0; } fprintf( stderr, " cleanup running\n" ); SIcbstat( si_ctx, SI_RET_UNREG, SI_CB_SECURITY ); SIcbstat( si_ctx, SI_RET_QUIT, SI_CB_SECURITY ); SItp_stats( si_ctx ); // drive for coverage only SItp_stats( NULL ); SIconnect( si_ctx, "127.0.0.1:43086" ); // ensure context has a tp block to free on shutdown SIshutdown( NULL ); SIabort( si_ctx ); // cleaning up the remaining global resources struct ginfo_blk *gptr = (struct ginfo_blk*)si_ctx; SItrash( TP_BLK, gptr->tplist ); free( gptr->tp_map ); free( gptr->rbuf ); free( gptr->cbtab ); free( si_ctx ); fprintf( stderr, " cleanup module finished with %d errors\n", errors ); return errors; } /* Address related tests. */ static int addr() { int errors = 0; int l; char buf1[4096]; // space to build buffers for xlation char* hr_addr; // human readable address returned void* net_addr; // a network address block of some type /* struct sockaddr* addr; addr = (struct sockaddr *) malloc( sizeof( struct sockaddr ) ); l = SIgenaddr( " [ff02::4]:4567", PF_INET6, IPPROTO_TCP, SOCK_STREAM, &addr ); SIgenaddr( " [ff02::4]:4567", PF_INET6, IPPROTO_TCP, SOCK_STREAM, &addr ); */ l = SIaddress( NULL, NULL, 0 ); errors += fail_if_true( l != 0, "SIaddress given two null pointers didn't return 0 len" ); l = SIaddress( buf1, NULL, 0 ); errors += fail_if_true( l != 0, "SIaddress given null dest pointer didn't return 0 len" ); l = SIaddress( NULL, (void *) &buf1, 0 ); errors += fail_if_true( l != 0, "SIaddress given null src pointer didn't return 0 len" ); net_addr = NULL; snprintf( buf1, sizeof( buf1 ), " [ff02::5:4001" ); // invalid address, drive leading space eater too l = SIaddress( buf1, (void **) &net_addr, AC_TOADDR6 ); errors += fail_if_true( l > 0, "to addr6 with bad addr convdersion returned valid len" ); free( net_addr ); snprintf( buf1, sizeof( buf1 ), "[ff02::5]:4002" ); // v6 might not be supported so failure is OK here; driving for coverage l = SIaddress( buf1, &net_addr, AC_TOADDR6 ); if( l > 0 ) { l = SIaddress( net_addr, (void *) &hr_addr, AC_TODOT ); // convert the address back to hr string errors += fail_if_true( l < 1, "v6 to dot conversion failed" ); errors += fail_if_nil( hr_addr, "v6 to dot conversion yields a nil pointer" ); free( net_addr ); free( hr_addr ); } snprintf( buf1, sizeof( buf1 ), "localhost:43086" ); l = SIaddress( buf1, (void **) &net_addr, AC_TOADDR ); errors += fail_if_true( l < 1, "v4 to addr conversion failed" ); l = SIaddress( net_addr, (void *) &hr_addr, AC_TODOT ); // convert the address back to hr string errors += fail_if_true( l < 1, "to dot convdersion failed" ); errors += fail_if_nil( hr_addr, "v4 to dot conversion yields a nil pointer" ); free( net_addr ); free( hr_addr ); fprintf( stderr, " addr module finished with %d errors\n", errors ); return errors; } /* Prep related tests. These mostly drive cases that aren't driven by "normal" connect, send, receive tests (e.g. UDP branches). */ static int prep() { int errors = 0; void* thing; // the thing that should be returned thing = SIlisten_prep( UDP_DEVICE, "localhost:1234", AF_INET ); errors += fail_if_nil( thing, "listen prep udp returned nil block" ); SItrash( TP_BLK, thing ); thing = SIlisten_prep( UDP_DEVICE, "localhost:1234", 84306 ); // this should fail errors += fail_not_nil( thing, "listen prep udp returned valid block ptr for bogus family" ); thing = SIconn_prep( si_ctx, UDP_DEVICE, "localhost:1234", 84306 ); // again, expect to fail; bogus family errors += fail_not_nil( thing, "conn prep udp returned valid block ptr for bogus family" ); return errors; } /* Polling/waiting tests. These are difficult at best because of the blocking nature of things, not to mention needing to have real ports open etc. */ static int poll() { int errors = 0; int status; struct ginfo_blk* dummy; dummy = SIinitialise( 0 ); // get one to fiddle to drive edge cases dummy->flags |= GIF_SHUTDOWN; // shutdown edge condition SIpoll( dummy, 1 ); free( dummy->tp_map ); free( dummy->rbuf ); free( dummy->cbtab ); memset( dummy, 0, sizeof( *dummy ) ); // force bad cookie check code to drive SIpoll( dummy, 1 ); free (dummy ); status = SIpoll( si_ctx, 1 ); errors += fail_if_true( status != 0, "poll failed" ); return errors; } /* Connection oriented tests. */ static int conn( ) { int errors = 0; int state; int cfd = 3; // fd for close char* buf; state = SIconnect( si_ctx, "localhost:4567" ); // driver regular connect errors += fail_if_true( state < 0, "connect to low port failed" ); state = SIconnect( si_ctx, "localhost:43086" ); // drive save connect with good return code errors += fail_if_true( state < 0, "connect to high port failed" ); tpem_set_addr_dup_state( 1 ); // force get sockket name emulation to return a duplicate address state = SIconnect( si_ctx, "localhost:43086" ); // drive save connect with good return code errors += fail_if_true( state >= 0, "forced dup connect did not return error" ); tpem_set_addr_dup_state( 0 ); // back to normal tpem_set_conn_state( -1 ); state = SIconnect( si_ctx, "localhost:4567" ); // driver regular connect errors += fail_if_true( state >= 0, "connect to low port successful when failure expected" ); tpem_set_conn_state( 3 ); tpem_set_sock_state( 1 ); // make scoket calls fail state = SIconnect( si_ctx, "localhost:4567" ); // driver regular connect errors += fail_if_true( state >= 0, "connect to low port successful when socket based failure expected" ); tpem_set_sock_state( 0 ); state = SIlistener( si_ctx, TCP_DEVICE, "0.0.0.0:4567" ); errors += fail_if_true( state < 0, "listen failed" ); tpem_set_bind_state( 1 ); state = SIlistener( si_ctx, TCP_DEVICE, "0.0.0.0:4567" ); errors += fail_if_true( state >= 0, "listen successful when bind error set" ); tpem_set_bind_state( 0 ); SIbldpoll( si_ctx ); // for coverage. no return value and nothing we can check state = SIclose( NULL, 0 ); //coverage errors += fail_if_true( state != SI_ERROR, "close given nil context returned success" ); state = SIclose( si_ctx, cfd ); errors += fail_if_true( state == SI_ERROR, "close given good context and good fd returned error" ); state = SIclose( si_ctx, 5000 ); // out of range fd errors += fail_if_true( state != SI_ERROR, "close given good context and bad fd returned success" ); state = SIclose( si_ctx, TCP_LISTEN_PORT ); // close listener errors += fail_if_true( state == SI_ERROR, "close given good context and listener fd returned error" ); state = SIclose( si_ctx, UDP_PORT ); // close first open udp port (should not be there) errors += fail_if_true( state != SI_ERROR, "close given good context and udp generic fd returned error" ); buf = SIgetname( 3 ); if( fail_if_true( buf == NULL, "get name failed to return a buffer" ) ) { errors++; } else { errors += fail_if_true( buf[0] == 0, "get name returned buf with emtpy string" ); free( buf ); } buf = SIgetname( -1 ); // invalid fd errors += fail_not_nil( buf, "get name returned buf with non-emtpy string when given bad fd" ); fprintf( stderr, " conn module finished with %d errors\n", errors ); return errors; } /* Misc tests that just don't fit in another bucket. */ static int misc( ) { int errors = 0; char buf[1024]; SIcbreg( NULL, SI_CB_SECURITY, test_cb, NULL ); // coverage only, no return value no verification SIcbreg( si_ctx, SI_CB_SECURITY, test_cb, NULL ); buf[0] = 0; SIgetaddr( si_ctx, buf ); errors += fail_if_true( buf[0] == 0, "get address failed" ); fprintf( stderr, " get address returns (%s)\n", buf ); fprintf( stderr, " misc module finished with %d errors\n", errors ); return errors; } /* New session (accept) testing. */ static int new_sess( ) { int errors = 0; char buf[1024]; struct tp_blk *tpptr; int status; tpptr = SInew( TP_BLK ); tpptr->fd = 3; tpptr->flags |= TPF_LISTENFD; tpem_set_accept_fd( -1 ); // accept will "fail" for coverage status = SInewsession( si_ctx, tpptr ); errors += fail_if_true( status != SI_ERROR, "newsession did not fail when accept fails" ); tpem_set_accept_fd( 5 ); // accept will return a good fd SIcbreg( si_ctx, SI_CB_SECURITY, test_cb_err, NULL ); // register error and drive new session for error coverage status = SInewsession( si_ctx, tpptr ); errors += fail_if_true( status >= 0, "newsession did failed when accept was good" ); tpem_set_accept_fd( 6 ); // accept will return a good fd SIset_tflags( si_ctx, SI_TF_NODELAY | SI_TF_FASTACK ); // flip options for coverage in new sess SIcbreg( si_ctx, SI_CB_CONN, test_cb, NULL ); // drive connection for coverage SIcbreg( si_ctx, SI_CB_SECURITY, test_cb, NULL ); status = SInewsession( si_ctx, tpptr ); errors += fail_if_true( status < 0, "newsession did failed when accept was good" ); free( tpptr ); fprintf( stderr, " new_sess module finished with %d errors\n", errors ); return errors; } /* Send tests */ static int send_tests( ) { int errors = 0; char buf[1024]; int len; int state; len = snprintf( buf, 100, "Heaven knows I'm miserable now!" ); state = SIsendt( si_ctx, 9999, buf, len ); errors += fail_if_true( state >= 0, "send given fd out of range did not fail" ); state = SIsendt( si_ctx, -1, buf, len ); errors += fail_if_true( state >= 0, "send given neg fd did not fail" ); SIsendt( si_ctx, 6, buf, len ); tpem_set_send_err( 99 ); SIsendt( si_ctx, 6, buf, len ); tpem_set_send_err( 0 ); tpem_set_sel_blk( 1 ); SIsendt( si_ctx, 6, buf, len ); tpem_set_sel_blk( 0 ); tpem_set_selef_fd( 6 ); // will cause send to fail and fd6 to close SIsendt( si_ctx, 6, buf, len ); return errors; } /* Wait testing. This is tricky because we don't have any sessions and thus it's difficult to drive much of SIwait(). */ static int wait_tests() { int errors = 0; struct ginfo_blk* dummy; dummy = SIinitialise( 0 ); // get one to fiddle to drive edge cases SIwait( dummy ); // malloc should "fail" dummy->flags |= GIF_SHUTDOWN; SIwait( dummy ); free( dummy->tp_map ); free( dummy->rbuf ); free( dummy->cbtab ); memset( dummy, 0, sizeof( *dummy ) ); // force bad cookie check code to drive SIwait( dummy ); free( dummy ); SIwait( si_ctx ); // should drive once through the loop return errors; } // ---------------------------------------------------------------------------------------- /* Drive tests... */ int main() { int errors = 0; rmr_set_vlevel( 5 ); // enable all debugging fprintf( stderr, "\n starting SI95 tests\n" ); errors += init(); errors += memory(); errors += addr(); errors += prep(); errors += conn(); errors += misc(); errors += new_sess(); // should leave a "connected" session at fd == 6 errors += send_tests(); errors += poll(); errors += wait_tests(); errors += cleanup(); test_summary( errors, "SI95 tests" ); if( errors == 0 ) { fprintf( stderr, " all tests were OK\n\n" ); } else { fprintf( stderr, " %d errors in SI95 core code\n\n", errors ); } return !!errors; }