Fix rmr_call() parameter checking bug
[ric-plt/lib/rmr.git] / test / rt_static_test.c
1 // : vi ts=4 sw=4 noet :
2 /*
3 ==================================================================================
4             Copyright (c) 2019 Nokia
5             Copyright (c) 2018-2019 AT&T Intellectual Property.
6
7    Licensed under the Apache License, Version 2.0 (the "License");
8    you may not use this file except in compliance with the License.
9    You may obtain a copy of the License at
10
11            http://www.apache.org/licenses/LICENSE-2.0
12
13    Unless required by applicable law or agreed to in writing, software
14    distributed under the License is distributed on an "AS IS" BASIS,
15    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16    See the License for the specific language governing permissions and
17    limitations under the License.
18 ==================================================================================
19 */
20
21 /*
22         Mmemonic:       rt_static_test.c
23         Abstract:       Test the route table funcitons. These are meant to be included at compile
24                                 time by the test driver.
25
26         Author:         E. Scott Daniels
27         Date:           3 April 2019
28 */
29
30 #include <unistd.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <strings.h>
34 #include <errno.h>
35 #include <string.h>
36 #include <stdint.h>
37 #include <pthread.h>
38 #include <semaphore.h>
39
40 #include "rmr.h"
41 #include "rmr_agnostic.h"
42
43 typedef struct entry_info {
44         int group;
45         char* ep_name;
46 } ei_t;
47
48
49 /*
50         Driven by symtab foreach element of one space.
51         We count using the data as a counter.
52 */
53 static void count_things( void* st, void* entry, char const* name, void* thing, void* vdata ) {
54         int* counter;
55
56         if( thing ) {
57                 if( (counter = (int *) vdata) != NULL ) {
58                         *counter++;
59                 }
60         }
61 }
62
63 /*
64         Returns the number of entries in the table for the given class.
65 */
66 static int count_entries( route_table_t* rt, int class ) {
67         int counter = 0;
68
69         if( ! rt ) {
70                 return 0;
71         }
72         if( !rt->hash ) {
73                 return 0;
74         }
75
76         rmr_sym_foreach_class( rt->hash, class, count_things, &counter );       // run each and update counter
77
78         return counter;
79 }
80
81 /*
82         Builds a route table key.
83 */
84 static uint64_t build_key( uint32_t mtype, uint32_t sid ) {
85         uint64_t k;
86
87         k = (uint64_t) sid << 32;
88         k += mtype;
89
90 fprintf( stderr, "<INFO> build key: %x %x --> %llx\n", (int) mtype, (int) sid, (long long) k );
91         return k;
92 }
93
94 /*
95         This is the main route table test. It sets up a very specific table
96         for testing (not via the generic setup function for other test
97         situations).
98 */
99 static int rt_test( ) {
100         uta_ctx_t* ctx;                 // context needed to test load static rt
101         route_table_t* rt;              // route table
102         route_table_t* crt;             // cloned route table
103         rtable_ent_t*   rte;    // route table entries from table
104         rtable_ent_t*   rte2;
105         endpoint_t*     ep;                     // endpoint added
106         endpoint_t*     ep2;
107         int more = 0;                   // more flag from round robin
108         int errors = 0;                 // number errors found
109         int     i;
110         int k;
111         int     c1;                                     // general counters
112         int c2;
113         int mtype;
114         int value;
115         int alt_value;
116         ei_t    entries[50];    // end point information
117         int             gcounts[7];             // number of groups in this set
118         int             ecounts[7];             // number of elements per group
119         uint64_t        mtypes[7];      // mtype/sid 'key' in the modern RMR world
120         char*   tok;
121         char*   nxt_tok;
122         int             enu = 0;
123         int             state;
124         char    *buf;
125         char*   seed_fname;             // seed file
126         SOCKET_TYPE     nn_sock;        // differnt in each transport (nng == struct, SI/Nano == int)
127         rmr_mbuf_t*     mbuf;           // message for meid route testing
128
129         #ifndef NNG_UNDER_TEST
130                 si_ctx_t* si_ctx = NULL;
131         #endif
132
133         setenv( "ENV_VERBOSE_FILE", ".ut_rmr_verbose", 1 );                     // allow for verbose code in rtc to be driven
134         i = open( ".ut_rmr_verbose", O_RDWR | O_CREAT, 0644 );
135         if( i >= 0 ) {
136                 write( i, "2\n", 2 );
137                 close( i );
138         }
139
140
141         /*
142                 The hacky code below calls the necessary rmr functions to create a route table
143                 as though the following were read and parsed by the rmr functions. (This tests
144                 the individual funcitons and avoids writing another parser, so it's not pretty.)
145
146                 mse | 0 | 0 | yahoo.com:4561,localhost:4562
147                 mse | 1 | 0 | localhost:4560,localhost:4568,localhost:4569; localhost:4561,localhost:4562
148                 mse | 2 | 0 | localhost:4563,localhost:4564
149                 mse | 3 | 0 | localhost:4565
150                 mse | 3 | 11 | locahost:5511
151                 mse | 3 | -1 | localhost:5500
152         */
153         gcounts[0] = 1;                                                 // first entry has 1 group with 2 endpoints; message type 0, sid 0
154         ecounts[0] = 2;
155         mtypes[0] = build_key( 0, 0 );                  // mtype is now a key of mtype/sid
156         entries[enu].group = 0; entries[enu].ep_name = "yahoo.com:4561"; enu++;         // use a dns resolvable name to test that
157         entries[enu].group = 0; entries[enu].ep_name = "localhost:4562"; enu++;         // rest can default to some dummy ip
158
159         gcounts[1] = 2;                         // 2 groups
160         ecounts[1] = 3;                         // first has 3 endpoints
161         mtypes[1] = build_key( 1, 0 );
162         entries[enu].group = 0; entries[enu].ep_name = "localhost:4560"; enu++;
163         entries[enu].group = 0; entries[enu].ep_name = "localhost:4568"; enu++;
164         entries[enu].group = 0; entries[enu].ep_name = "localhost:4569"; enu++;
165
166         gcounts[2] = 0;                                 // 0 means use same rte, this is the next group for the entry
167         ecounts[2] = 2;                                 // 2 endpoints
168         mtypes[2] = 999;                                // ignored when appending to previous entry
169         entries[enu].group = 1; entries[enu].ep_name = "localhost:4561"; enu++;
170         entries[enu].group = 1; entries[enu].ep_name = "localhost:4562"; enu++;
171
172         gcounts[3] = 1;                                 // next entry has 1 group
173         ecounts[3] = 2;                                 // with 2 enpoints
174         mtypes[3] = build_key( 2, 0 );
175         entries[enu].group = 0; entries[enu].ep_name = "localhost:4563"; enu++;
176         entries[enu].group = 0; entries[enu].ep_name = "localhost:4564"; enu++;
177
178         gcounts[4] = 1;                                 // three entries for mt==3 with different sids
179         ecounts[4] = 1;
180         mtypes[4] = build_key( 3, 0 );
181         entries[enu].group = 0; entries[enu].ep_name = "localhost:5500"; enu++;
182
183         gcounts[5] = 1;
184         ecounts[5] = 1;
185         mtypes[5] = build_key( 3, 11 );
186         entries[enu].group = 0; entries[enu].ep_name = "localhost:5511"; enu++;
187
188         gcounts[6] = 1;
189         ecounts[6] = 1;
190         mtypes[6] = build_key( 3, -1 );
191         entries[enu].group = 0; entries[enu].ep_name = "localhost:5512"; enu++;
192
193
194         rt = uta_rt_init( );                                                                            // get us a route table
195         if( (errors += fail_if_nil( rt, "pointer to route table" )) ) {
196                 fprintf( stderr, "<FAIL> abort: cannot continue without a route table\n" );
197                 exit( 1 );
198         }
199
200         enu = 0;
201         rte = NULL;
202         for( i = 0; i < sizeof( gcounts )/sizeof( int ); i++ ) {                                // add entries defined above
203                 if( gcounts[i] ) {
204                         rte = uta_add_rte( rt, mtypes[i], gcounts[i] );                                 // get/create entry for message type
205                         if( (errors += fail_if_nil( rte, "route table entry" )) ) {
206                                 fprintf( stderr, "<FAIL> abort: cannot continue without a route table entry\n" );
207                                 exit( 1 );
208                         }
209                 } else {
210                         if( rte == NULL ) {
211                                 fprintf( stderr, "<SNAFU> internal testing error -- rte was nil for gcount == 0\n" );
212                                 exit( 1 );
213                         }
214                 }
215
216                 for( k = 0; k < ecounts[i]; k++ ) {
217                         ep = uta_add_ep( rt, rte, entries[enu].ep_name, entries[enu].group );
218                         errors += fail_if_nil( ep, "endpoint" );
219                         enu++;
220                 }
221         }
222
223         // ----- end hacking together a route table ---------------------------------------------------
224
225
226         crt = uta_rt_clone( rt );                                                               // clone only the endpoint entries
227         errors += fail_if_nil( crt, "cloned route table" );
228         if( crt ) {
229                 c1 = count_entries( rt, 1 );
230                 c2 = count_entries( crt, 1 );
231                 errors += fail_not_equal( c1, c2, "cloned (endpoints) table entries space 1 count (b) did not match original table count (a)" );
232
233                 c2 = count_entries( crt, 0 );
234                 errors += fail_not_equal( c2, 0, "cloned (endpoints) table entries space 0 count (a) was not zero as expected" );
235                 uta_rt_drop( crt );
236         }
237
238
239         crt = uta_rt_clone_all( rt );                                                   // clone all entries
240         errors += fail_if_nil( crt, "cloned all route table" );
241
242         if( crt ) {
243                 c1 = count_entries( rt, 0 );
244                 c2 = count_entries( crt, 0 );
245                 errors += fail_not_equal( c1, c2, "cloned (all) table entries space 0 count (b) did not match original table count (a)" );
246
247                 c1 = count_entries( rt, 1 );
248                 c2 = count_entries( crt, 1 );
249                 errors += fail_not_equal( c1, c2, "cloned (all) table entries space 1 count (b) did not match original table count (a)" );
250                 uta_rt_drop( crt );
251         }
252
253         #ifdef NNG_UNDER_TEST
254                 if( (ctx = (uta_ctx_t *) malloc( sizeof( uta_ctx_t ) )) != NULL ) {             // get a "context" needed for si testing
255                         memset( ctx, 0, sizeof( *ctx ) );
256                         ctx->rtable = rt;
257                 } else {
258                         fprintf( stderr, "<FAIL> cannot acllocate a context, cannot continue rtable tests\n" );
259                         return errors;
260                 }
261         #else
262                 ctx = mk_dummy_ctx();
263         #endif
264
265         ctx->rtable = rt;
266
267         ep = uta_get_ep( rt, "localhost:4561" );
268         errors += fail_if_nil( ep, "end point (fetch by name)" );
269         ep = uta_get_ep( rt, "bad_name:4560" );
270         errors += fail_not_nil( ep, "end point (fetch by name with bad name)" );
271
272         ep = NULL;
273         #ifdef NNG_UNDER_TEST
274                 state = uta_epsock_byname( rt, "localhost:4561", &nn_sock, &ep );               // this should be found
275         #else
276                 state = uta_epsock_byname( ctx, "localhost:4561", &nn_sock, &ep );      // this should be found
277         #endif
278         errors += fail_if_equal( state, 0, "socket (by name)" );
279         errors += fail_if_nil( ep, "epsock_byname did not populate endpoint pointer when expected to" );
280         //alt_value = uta_epsock_byname( rt, "localhost:4562" );                        // we might do a memcmp on the two structs, but for now nothing
281         //errors += fail_if_equal( value, alt_value, "app1/app2 sockets" );
282
283         #if  NNG_UNDER_TEST
284                 state = uta_epsock_byname( NULL, "localhost:4561", &nn_sock, &ep );             // test coverage on nil checks
285         #else
286                 state = uta_epsock_byname( NULL, "localhost:4561", &nn_sock, &ep );
287         #endif
288         errors += fail_not_equal( state, 0, "socket (by name) nil check returned true" );
289
290         ep->open = 1;
291         #if  NNG_UNDER_TEST
292                 state = uta_epsock_byname( rt, "localhost:4561", &nn_sock, NULL );              // test coverage on nil checks
293         #else
294                 state = uta_epsock_byname( ctx, "localhost:4561", &nn_sock, NULL );
295         #endif
296         errors += fail_if_equal( state, 0, "socket (by name) open ep check returned false" );
297
298
299         // --- test that the get_rte function finds expected keys, and retries to find 'bad' sid attempts for valid mtypes with no sid
300         rte = uta_get_rte( rt, 0, 1, TRUE );                    // s=0 m=1 is defined, so this should return a pointer
301         errors += fail_if_nil( rte, "get_rte did not return a pointer when s=0 m=1 true given" );
302
303         rte = uta_get_rte( rt, 0, 1, FALSE );                   // the retry shouldn't apply, but ensure it does the righ thing
304         errors += fail_if_nil( rte, "get_rte did not return a pointer when s=0 m=1 false given" );
305
306         rte = uta_get_rte( rt, 1000, 1, FALSE );                // s=1000 does not exist for any msg type; should return nil as not allowed to drop sid
307         errors += fail_not_nil( rte, "get_rte returned a pointer when s=1000 m=1 false given" );
308
309         rte = uta_get_rte( rt, 1000, 1, TRUE );                 // this should also fail as there is no mt==1 sid==-1 defined
310         errors += fail_not_nil( rte, "get_rte returned a pointer when s=1000 m=1 true given" );
311
312         rte = uta_get_rte( rt, 0, 3, TRUE );                    // mtype sid combo does exist; true/false should not matter
313         errors += fail_if_nil( rte, "get_rte did not return a pointer when s=0 m=3 true given" );
314
315         rte2 = uta_get_rte( rt, 11, 3, TRUE );                  // same mtype as before, different (valid) group, rte should be different than before
316         errors += fail_if_nil( rte2, "get_rte did not return a pointer when s=11 m=3 true given" );
317         errors += fail_if_true( rte == rte2, "get_rte for mtype==3 and different sids (0 and 11) returned the same rte pointer" );
318
319         rte2 = uta_get_rte( rt, 0, 3, FALSE );                  // since the mtype/sid combo exists, setting false should return the same as before
320         errors += fail_if_nil( rte2, "get_rte did not return a pointer when s=0 m=3 false given" );
321         errors += fail_if_false( rte == rte2, "get_rte did not return same pointer when mtype/sid combo given with different true/false" );
322
323         rte = uta_get_rte( rt, 12, 3, FALSE );                  // this combo does not exist and should fail when alt-key is not allowed (false)
324         errors += fail_not_nil( rte, "get_rte returned a pointer for s=12, m=3, false" );
325
326         rte = uta_get_rte( rt, 12, 3, TRUE );                   // this should return the entry for the 3/-1 combination
327         errors += fail_if_nil( rte, "get_rte did not return a pointer for s=12, m=3, true" );
328
329
330         alt_value = -1;
331         rte = uta_get_rte( rt, 0, 1, FALSE );                   // get an rte for the next loop
332         if( rte ) {
333                 for( i = 0; i < 10; i++ ) {                                                                     // round robin return value should be different each time
334                         #ifdef NNG_UNDER_TEST
335                                 value = uta_epsock_rr( rte, 0, &more, &nn_sock, &ep );          // msg type 1, group 1
336                         #else
337                                 value = uta_epsock_rr( ctx, rte, 0, &more, &nn_sock, &ep );
338                         #endif
339
340                         errors += fail_if_equal( value, alt_value, "round robiin sockets with multiple end points" );
341                         errors += fail_if_false( more, "more for mtype==1" );
342                         alt_value = value;
343                 }
344         }
345
346         more = -1;
347         rte = uta_get_rte( rt, 0, 3, FALSE );                           // get an rte for the next loop
348         if( rte ) {
349                 for( i = 0; i < 10; i++ ) {                                                             // this mtype has only one endpoint, so rr should be same each time
350                         #ifdef NNG_UNDER_TEST
351                                 value = uta_epsock_rr( rte, 0, NULL, &nn_sock, &ep );           // also test ability to deal properly with nil more pointer
352                         #else
353                                 value = uta_epsock_rr( ctx, rte, 0, NULL, &nn_sock, &ep );
354                         #endif
355
356                         if( i ) {
357                                 errors += fail_not_equal( value, alt_value, "round robin sockets with one endpoint" );
358                                 errors += fail_not_equal( more, -1, "more value changed in single group instance" );
359                         }
360                         alt_value = value;
361                 }
362         }
363
364         rte = uta_get_rte( rt, 11, 3, TRUE );
365         #ifdef NNG_UNDER_TEST
366                 state = uta_epsock_rr( rte, 22, NULL, NULL, &ep );
367         #else
368                 state = uta_epsock_rr( ctx, rte, 22, NULL, NULL, &ep );
369         #endif
370         errors += fail_if_true( state, "uta_epsock_rr returned bad (non-zero) state when given nil socket pointer" );
371
372
373         uta_rt_clone( NULL );                                                           // verify null parms don't crash things
374         uta_rt_drop( NULL );
375         #ifdef NNG_UNDER_TEST
376                 uta_epsock_rr( NULL, 0,  &more, &nn_sock, &ep );                        // drive null case for coverage
377                 state = uta_epsock_rr( rte, 22, NULL, NULL, &ep );
378         #else
379                 state = uta_epsock_rr( NULL, NULL, 0,  &more, &nn_sock, &ep );                  // drive null case for coverage
380                 errors += fail_not_equal( state, 0, "uta_epsock_rr did not return false when given nil ctx" );
381
382                 state = uta_epsock_rr( ctx, NULL, 0,  &more, &nn_sock, &ep );
383                 errors += fail_not_equal( state, 0, "uta_epsock_rr did not return false when given nil rte" );
384
385                 state = uta_epsock_rr( ctx, rte, 10000,  &more, &nn_sock, &ep );
386                 errors += fail_not_equal( state, 0, "uta_epsock_rr did not return false when given invalid group number" );
387         #endif
388         uta_add_rte( NULL, 99, 1 );
389         uta_get_rte( NULL, 0, 1000, TRUE );
390
391         fprintf( stderr, "[INFO] test: adding end points with nil data; warnings expected\n" );
392         uta_add_ep( NULL, NULL, "foo", 1 );
393         uta_add_ep( rt, NULL, "foo", 1 );
394
395         buf = uta_fib( ".gitignore" );
396         errors += fail_if_nil( buf, "buffer from read file into buffer" );
397         if( buf ) {
398                 free( buf );
399         }
400         buf = uta_fib( "no-file" );
401         errors += fail_if_nil( buf, "buffer from read file into buffer (no file)" );
402         if( buf ) {
403                 free( buf );
404         }
405
406         uta_rt_drop( rt );
407         rt = NULL;
408
409
410         if( ctx ) {
411                 if( (seed_fname = getenv( "RMR_SEED_RT" )) != NULL ) {
412                         read_static_rt( ctx, 0 );
413                         rt = ctx->rtable;
414                         errors += fail_if_nil( rt, "read seed table didn't generate a rtable pointer in context" );
415                         unsetenv( "RMR_SEED_RT" );                              // remove for next test
416                 }
417
418                 read_static_rt( ctx, 0 );                       // drive for not there coverage
419         }
420
421
422         buf = uta_fib( "no-suhch-file" );                       // drive some error checking for coverage
423         if( buf ) {
424                 free( buf );
425         }
426
427
428         ep = (endpoint_t *) malloc( sizeof( *ep ) );
429         pthread_mutex_init( &ep->gate, NULL );
430         ep->name = strdup( "worm" );
431         ep->addr = NULL;
432         #ifdef NNG_UNDER_TEST
433                 state = uta_link2( ep );
434         #else
435                 state = uta_link2( ctx, ep );
436         #endif
437         errors += fail_if_true( state, "link2 did not return false when given a bad target name" );
438
439         #ifdef NNG_UNDER_TEST
440                 state = uta_link2( NULL );
441         #else
442                 state = uta_link2( ctx, NULL );
443                 errors += fail_if_true( state, "link2 did not return false when given nil ep pointer" );
444
445                 state = uta_link2( NULL, ep );
446         #endif
447         errors += fail_if_true( state, "link2 did not return false when given nil pointer" );
448
449         ep->name = strdup( "localhost:5512" );
450         ep->open = 1;
451         #ifdef NNG_UNDER_TEST
452                 state = uta_link2( ep );                        // drive for coverage
453         #else
454                 state = uta_link2( ctx, ep );
455         #endif
456         errors += fail_if_false( state, "link2 did returned false when given open ep" );
457
458         #ifndef NNG_UNDER_TEST
459                 ep->open = 0;                                                   // context is used only if ep not open, so to check this test close the ep
460                 state = rt_link2_ep( NULL, ep );
461                 errors += fail_if_true( state, "rt_link2_ep returned true when given bad context" );
462
463                 state = rt_link2_ep( ctx, NULL );
464                 errors += fail_if_true( state, "rt_link2_ep returned true when given bad ep" );
465
466                 ep->open = 1;
467                 state = rt_link2_ep( ctx, ep );
468                 errors += fail_if_false( state, "rt_link2_ep returned false when given an open ep" );
469
470                 ep->open = 0;
471                 state = rt_link2_ep( ctx, ep );
472                 errors += fail_if_false( state, "rt_link2_ep returned false when given a closed ep" );
473
474                 ep->open = 1;
475                 uta_ep_failed( ep );
476                 errors += fail_if_true( ep->open, "uta_ep_failed didn't set open flag to false" );
477
478         #endif
479
480
481         // ----------------- test the meid support for looking up an endpoint based on the meid in the message -----
482
483         ctx->rtable = NULL;
484         ctx->my_name = strdup( "my_host_name" );                // set up to load a rtable
485         ctx->my_ip = strdup( "192.168.1.30" );
486         gen_rt( ctx );                                                                  // generate a route table with meid entries and hang off ctx
487
488         mbuf = rmr_alloc_msg( ctx, 2048 );               //  buffer to play with
489         mbuf->len = 100;
490         rmr_str2meid( mbuf, "meid1" );                                  // id that we know is in the map
491
492         #ifdef NNG_UNDER_TEST
493                 ep = NULL;                                                                              // force to nil so we see it go non-nil
494                 state = epsock_meid( ctx->rtable, mbuf, &nn_sock, &ep );
495                 errors += fail_if_nil( ep, "ep was nil when looking up ep with known meid in message" );
496                 errors += fail_not_equal( state, 1, "state was not true when looking up ep with known meid in message" );
497
498                 rmr_str2meid( mbuf, "XXXmeid1" );                               // id that we know is NOT in the map
499                 state = epsock_meid( ctx->rtable, mbuf, &nn_sock, &ep );
500                 // it is NOT a valid check to test ep for nil -- epsock_mied doesn't guarentee ep is set/cleared when state is false
501                 errors += fail_not_equal( state, 0, "state was not false when looking up ep with unknown meid in message" );
502         #else
503                 ep = NULL;                                                                              // force to nil so we see it go non-nil
504                 state = epsock_meid( ctx, ctx->rtable,  mbuf, &nn_sock, &ep );
505                 errors += fail_if_nil( ep, "ep was nil when looking up ep with known meid in message" );
506                 errors += fail_not_equal( state, 1, "state was not true when looking up ep with known meid in message" );
507
508                 state = epsock_meid( ctx, ctx->rtable,  mbuf, &nn_sock, &ep );          // a second call to drive open == true check for coverage
509                 errors += fail_if_nil( ep, "ep was nil when looking up ep with known meid in message; on open ep" );
510                 errors += fail_not_equal( state, 1, "state was not true when looking up ep with known meid in message; on open ep" );
511
512                 rmr_str2meid( mbuf, "XXXmeid1" );                               // id that we know is NOT in the map
513                 state = epsock_meid( ctx, ctx->rtable, mbuf, &nn_sock, &ep );
514                 // it is NOT a valid check to test ep for nil -- epsock_mied doesn't guarentee ep is set/cleared when state is false
515                 errors += fail_not_equal( state, 0, "state was not false when looking up ep with unknown meid in message" );
516
517                 state = epsock_meid( NULL, ctx->rtable,  mbuf, &nn_sock, &ep );
518                 errors += fail_not_equal( state, 0, "epsock_meid returned true when given nil context" );
519
520                 state = epsock_meid( ctx, ctx->rtable,  mbuf, NULL, &ep );
521                 errors += fail_not_equal( state, 0, "epsock_meid returned true when given nil socket pointer" );
522         #endif
523
524
525         // ------------- si only; fd to ep conversion functions ---------------------------------------------------------
526         #ifndef NNG_UNDER_TEST
527                 ep2 = (endpoint_t *) malloc( sizeof( *ep ) );
528
529                 fd2ep_init( ctx );
530                 fd2ep_add( ctx, 10, ep2 );
531
532                 ep = fd2ep_get( ctx, 10 );
533                 errors += fail_if_nil( ep, "fd2ep did not return pointer for known mapping" );
534                 errors += fail_if_false( ep == ep2,  "fd2ep did not return same pointer that was added" );
535
536                 ep = fd2ep_get( ctx, 20 );
537                 errors += fail_not_nil( ep, "fd2ep did returned a pointer for unknown mapping" );
538
539                 ep = fd2ep_del( ctx, 10 );
540                 errors += fail_if_nil( ep, "fd2ep delete did not return pointer for known mapping" );
541                 errors += fail_if_false( ep == ep2,  "fd2ep delete did not return same pointer that was added" );
542
543                 ep = fd2ep_del( ctx, 20 );
544                 errors += fail_not_nil( ep, "fd2ep delete returned a pointer for unknown mapping" );
545         #endif
546
547         return !!errors;                        // 1 or 0 regardless of count
548 }