Line data Source code
1 : #ifndef HEADER_fd_src_app_shared_fd_tile_unit_test_h
2 : #define HEADER_fd_src_app_shared_fd_tile_unit_test_h
3 :
4 : #include "../../app/shared/fd_config.h"
5 :
6 : /* We want a way to test the transitions between the tile's callbacks
7 : (before/after_credit,before/during/after_frag, housekeeping). To
8 : that end, we need to instantiate the tile's context (e.g.
9 : fd_net_ctx_t, fd_pack_ctx_t, etc), which requires calling
10 : unprivileged_init, or even privileged_init. We therefore want to
11 : bring up the part of the topology that's relevant to the
12 : tile under testing, such as the upstream producer links, downstream
13 : consumer links, etc. This approach loads the config TOML and
14 : invokes fd_topo_initialize from either Frankendancer or Firedancer
15 : topology, so that the tile is configured the same way as it would
16 : be in production running on mainnet or testnet.
17 : We don't want to treat the tile as a black box, and therefore
18 : cannot use fd_stem.c, so we include the following template to bring
19 : some structures to the test. This is a basic structure for tile
20 : unit testing and is not meant to be an exhaustive API, but is
21 : expected to evolve over time: specific tiles may require manual
22 : configuration at initialization and during testing. */
23 :
24 : /* fd_tile_unit_test_init provides the skeleton initialization steps.
25 : From the three config paths, only default_topo_config_path is
26 : required, whereas the other two (override_topo_config_path and
27 : user_topo_config_path) are optional. These inputs, together with
28 : netns, is_firedancer and is_local_cluster are passed to
29 : fd_config_load() (Refer to the functions documentation for further
30 : details). fd_topo_initialize_ is a pointer to the initialization
31 : function inside the chosen topology (e.g. firedancer or fdctl).
32 : topo_run_tile is typically declared and defined inside the tile
33 : under test. out_config is populated as part of the initialization
34 : process. On error, the function logs a warning and returns NULL.
35 : On success, it return a (fd_topo_tile_t *) pointer, which is
36 : typically required by (un)priviliged_init. */
37 :
38 : fd_topo_tile_t *
39 : fd_tile_unit_test_init( char const * default_topo_config_path,
40 : char const * override_topo_config_path,
41 : char const * user_topo_config_path,
42 : int netns,
43 : int is_firedancer,
44 : int is_local_cluster,
45 : void (*fd_topo_initialize_)(config_t *),
46 : fd_topo_run_tile_t * topo_run_tile,
47 : config_t * out_config );
48 :
49 : /* The following provides a generic, templated framework for unit
50 : testing Firedancer tiles under single-threaded environment. It
51 : allows different tiles (pack, net, shred, etc.) to share common
52 : testing infrastructure using C preprocessor templates. This
53 : framework focuses on testing the tile states across the
54 : before/after_credit and before/during/after_frag callbacks while
55 : using the topology instantiated like full Firedancer/Frankendancer.
56 :
57 : Typical usage:
58 :
59 : // Include the tile implementation and any needed headers at the
60 : // top of the tile unit test file to access any callbacks or
61 : // types declaration
62 :
63 : #include "fd_example_tile.c"
64 : #include "fd_example.h"
65 :
66 : // Define the test context fields local to the tile to track
67 : // test states across callback transitions.
68 :
69 : struct fd_tile_test_locals {
70 : ulong after_credit_expected_sz;
71 : example_t * after_credit_expected_output;
72 :
73 : uint after_frag_expected_sz;
74 : ...
75 : };
76 : // The struct fd_tile_test_locals will be typedef as
77 : // fd_tile_test_locals_t and included in struct fd_tile_test_context
78 : // discussed later.
79 :
80 : struct fd_tile_test_context {
81 : ulong loop_i;
82 : void (*select_in_link) (fd_tile_test_link_t ** test_links, fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE *);
83 : void (*select_out_links) (fd_tile_test_link_t ** test_links, fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE *);
84 : ....
85 : fd_tile_test_locals_t locals[ 1 ]; // Tile specific test fields.
86 : };
87 :
88 : // Define the tile's context type
89 :
90 : #define TEST_TILE_CTX_TYPE fd_example_ctx_t
91 :
92 : will replace all TEST_TILE_CTX_TYPE in the test template with fd_example_ctx_t
93 :
94 : // Define test callbacks, similar to stem
95 :
96 : #define TEST_CALLBACK_BEFORE_CREDIT before_credit
97 : #define TEST_CALLBACK_AFTER_CREDIT after_credit
98 : #define TEST_CALLBACK_BEFORE_FRAG before_frag
99 : #define TEST_CALLBACK_DURING_FRAG during_frag
100 : #define TEST_CALLBACK_AFTER_FRAG after_frag
101 : #define TEST_CALLBACK_HOUSEKEEPING during_housekeeping
102 :
103 : // Include this framework header and template
104 : #include "../../app/shared/fd_tile_unit_test.h"
105 : #include "../../app/shared/fd_tile_unit_test_tmpl.c"
106 :
107 : Will generate the following structures that can be used for any
108 : upstream frag producers/verifiers and downstream frag verifiers
109 : during the test:
110 :
111 : // All TEST_TILE_CTX_TYPE will be replaced by
112 : // fd_example_ctx_t at compile time.
113 :
114 : typedef struct fd_tile_test_context fd_tile_test_ctx_t;
115 : struct fd_tile_test_context {
116 : ...
117 : fd_tile_test_locals_t locals[ 1 ];
118 : };
119 : // Contains both common testing infrastructure and
120 : // tile-specific state.
121 :
122 : typedef struct fd_tile_test_link fd_tile_test_link_t;
123 : struct fd_tile_test_link {
124 : ....
125 : };
126 : // This structure models the Firedancer communication
127 : // channels (fd_topo_link_t) between tiles. The test
128 : // template will initialize these fields by calling
129 : // fd_tile_test_init_link_in/out that are discussed later.
130 :
131 : And will generate the following APIs:
132 : - TEST_TILE_CTX_TYPE below will be replaced by actual context
133 : type (e.g. fd_example_ctx_t):
134 :
135 : void
136 : fd_tile_test_init_link_in( ..... );
137 : void
138 : fd_tile_test_init_link_out( ..... );
139 : // Find the link in the topology according link_name, and
140 : // initialize the test link with callbacks for frags
141 : // generation and verification.
142 :
143 : void
144 : fd_tile_test_reset_env( .... );
145 : // Reset test environment between test runs.
146 : // Must be called before a new test run.
147 :
148 : void
149 : fd_tile_test_check_output( ... );
150 : // Notify test to verify output when the tested tile is
151 : // expected to produce frags after a tile callback.
152 :
153 : void
154 : fd_tile_test_run( ... );
155 : // Main test execution function
156 :
157 : // Custom functions to be implemented in test_xxx_tile.c:
158 :
159 : // Each run of test needs its own way of selecting an input link
160 : // and output links. Each input/upstream/producer link usually has
161 : // its own input generator, such as publish(...) and make_sig(...),
162 : // and therefore their own state verifier.
163 : // Each output/downstream/consumer link usually has its own output
164 : // verifier.
165 : // Examples:
166 :
167 : static void
168 : run1_select_in_link{ ... test_links,
169 : test_ctx,
170 : tile_ctx } {
171 : test_ctx->in_link = fd_uint_if( test_ctx->loop%2, test_links[ 0 ], NULL );
172 : };
173 : // Must set the test_ctx->in_link to NULL if no producer.
174 :
175 : static void
176 : run1_select_out_links( ... test_links,
177 : ... test_ctx,
178 : ... tile_ctx {
179 : if( test_ctx->loop%2 ) fd_tile_test_check_output( FD_TILE_TEST_CALLBACK_AFTER_CREDIT, test_links[ 1 ] );
180 : };
181 : // Must call fd_tile_test_check_output if expect the tile to produce a frag
182 :
183 : static ulong
184 : link1_publish( ... test_ctx,
185 : ... input_link ) {
186 : void * frag = get_test_vector( test_ctx, input_link );
187 : test_ctx->filter = !is_valid_frag( frag ); // set expected filter for before_frag
188 : return frag_sz;
189 : };
190 :
191 : static int
192 : link2_out_check(... test_ctx,
193 : ... tile_ctx,
194 : ... link2 ) {
195 : fd_frag_meta_t * mline = link2->mcache + fd_mcache_line_idx( link2->prod_seq, link2->depth );
196 : ulong out_mem = (ulong)fd_chunk_to_laddr( (void *)link2->base, link2->chunk ) + mline->ctl;
197 : if( !verify_output_vector( out_mem ) ) {
198 : FD_LOG_WARNING(("output unmatched"));
199 : return -1;
200 : }
201 : return 0;
202 : };
203 :
204 : static void
205 : populate_test_vectors( fd_tile_test_ctx_t * test_ctx ) {
206 : for( ulong i=0; i<MAX_TEST; i++ ) {
207 : make_input( i );
208 : make_output( i );
209 : }
210 : }
211 : // Populate any vectors ahead of time for testing later
212 :
213 : static void
214 : mock_privileged_init( fd_topo_t * topo,
215 : fd_topo_tile_t * tile ) {
216 : void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id );
217 : FD_SCRATCH_ALLOC_INIT( l, scratch );
218 : fd_example_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_example_ctx_t ), sizeof( fd_example_ctx_t ) );
219 : ....
220 : }
221 : // Often we need to manually replicate some work in priviledged_init.
222 :
223 : static void
224 : example_reset( fd_tile_test_ctx_t * test_ctx,
225 : fd_example_ctx_t * ctx,
226 : ulong test_choice1,
227 : ulong test_choice2 ) {
228 : test_ctx->locals->choice1 = ...;
229 : test_ctx->locals->choice2 = ...;
230 : }
231 : // Reset the test context according to some self-defined logic
232 : // for a fresh test run. Usually called after fd_tile_test_reset_env.
233 :
234 : # main typically looks like:
235 : int
236 : main( int argc, char ** argv ) {
237 : fd_boot( &argc, &argv );
238 :
239 : // Initialize tile unit test
240 : char const * default_topo_config_path = TEST_DEFAULT_TOPO_CONFIG_PATH;
241 : char const * override_topo_config_path = NULL;
242 : char const * user_topo_config_path = NULL;
243 : int netns = 0;
244 : int is_firedancer = TEST_IS_FIREDANCER;
245 : int is_local_cluster = 0;
246 : fd_topo_tile_t * test_tile = fd_tile_unit_test_init( default_topo_config_path, override_topo_config_path, user_topo_config_path,
247 : netns, is_firedancer, is_local_cluster,
248 : fd_topo_initialize, &fd_tile_pack, config );
249 : FD_TEST( test_tile );
250 : fd_metrics_register( fd_metrics_new( metrics_scratch, 10, 10 ) );
251 :
252 : mock_privileged_init( &config->topo, test_tile );
253 : unprivileged_init( &config->topo, test_tile );
254 :
255 : fd_tile_test_link_t input_link = {0};
256 : fd_tile_test_init_link_in( &config->topo, &input_link, "<input_tile>_<tested_tile>", ctx, find_in_index,
257 : link1_publish, NULL, NULL, link1_during_frag_check, link1_after_frag_check );
258 : fd_tile_test_link_t output_link = {0};
259 : fd_tile_test_init_link_out( &config->topo, &output_link, "<tested_tile>_<output_tile>", link2_out_check );
260 : fd_tile_test_link_t * test_links[ 2 ] = { input_link, output_link };
261 : ....
262 :
263 : populate_test_vectors( &test_ctx );
264 :
265 : // Tile test run 1
266 : fd_tile_test_reset_env( &test_ctx, &stem, test_links,
267 : run1_select_in_link, run1_select_out_link, bc_check1, ac_check1 );
268 : example_reset( &test_ctx, ctx, 1, 0 );
269 : fd_tile_test_run( ctx, &stem, test_links, &test_ctx, 12, 6 );
270 :
271 : // Tile test run 2
272 : fd_tile_test_update_callback_link_in( &input_link, FD_TILE_TEST_CALLBACK_PUBLISH, publish2, NULL );
273 : fd_tile_test_reset_env( &test_ctx, &stem, test_links,
274 : stress_test_select_in_link, stress_test_select_out_link, bc_check2, ac_check2 );
275 : example_reset( &test_ctx, ctx, 1, 0 );
276 : fd_tile_test_run( ctx, &stem, test_links, &test_ctx, 200000, 1000 );
277 :
278 : // Tile test run 3
279 : fd_tile_test_update_callback_link_in
280 : fd_tile_test_reset_env...
281 : example_reset...
282 : fd_tile_test_run...
283 :
284 : ...
285 : }
286 : */
287 :
288 : #ifdef TEST_TILE_CTX_TYPE
289 :
290 : typedef struct fd_tile_test_link fd_tile_test_link_t;
291 : typedef struct fd_tile_test_context fd_tile_test_ctx_t;
292 :
293 : /* Represents an input/output link for the tile under test.
294 : This structure models the Firedancer communication channels
295 : (fd_topo_link_t) between tiles. The test template will initialize
296 : these fields by calling fd_tile_test_init_link_in/out.
297 : Each check function should return 0 on success, error code on
298 : failure and log warnings. The fd_tile_test_run will abort the test
299 : on error code. */
300 : struct fd_tile_test_link {
301 : fd_frag_meta_t * mcache; // mcache of the link
302 : void * dcache; // dcache of the link
303 : ulong depth; // depth of the link
304 : const void * base; // base of dcache == fd_wksp_containing( link->dcache ).
305 : ulong chunk0; // dcache chunk0 == fd_dcache_compact_chunk0( link->base, link->dcache )
306 : ulong wmark; // dcache chunk watermark == fd_dcache_compact_wmark( link->base, link->dcache, link->mtu );
307 : ulong chunk; // current chunk. initialized to chunk0
308 : ulong in_idx; // index of this link in the context if this link is an input link, ULONG_MAX otherwise.
309 :
310 : ulong prod_seq; // seq of newly produced frag. initialized to 0, and incremented everytime when a new frag is produced from this link
311 : ulong cons_seq; // seq of currently processing frag. initialized to ULONG_MAX, and incremented everytime before the test loop processes a frag
312 :
313 : int is_input_link; // 1 the link is an input/producer/upstream link, 0 if the link is an output/consumer/downstream link
314 :
315 : // for input/producer/upstream link
316 : ulong (*publish) ( fd_tile_test_ctx_t *, fd_tile_test_link_t * ); // callback to publish a frag
317 : ulong (*make_sig)( fd_tile_test_ctx_t *, fd_tile_test_link_t * ); // callback to make signature for the published frag
318 : int (*before_frag_check)( fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE * ); // callback to check before_frag
319 : int (*during_frag_check)( fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE * ); // callback to check during_frag
320 : int (*after_frag_check) ( fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE * ); // callback to check after_frag
321 :
322 : // for output/consumer/downstream link
323 : int (*output_verifier) ( fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE *, fd_tile_test_link_t * ); // callback to verify an output
324 : };
325 :
326 8460 : #define FD_TILE_TEST_CALLBACK_BEFORE_CREDIT 0
327 15252 : #define FD_TILE_TEST_CALLBACK_AFTER_CREDIT 1
328 : #define FD_TILE_TEST_CALLBACK_BEFORE_FRAG 2
329 0 : #define FD_TILE_TEST_CALLBACK_DURING_FRAG 3
330 3538 : #define FD_TILE_TEST_CALLBACK_AFTER_FRAG 4
331 48 : #define FD_TILE_TEST_CALLBACK_PUBLISH 5
332 0 : #define FD_TILE_TEST_CALLBACK_MAKE_SIG 6
333 0 : #define FD_TILE_TEST_CALLBACK_OUT_VERIFY 7
334 :
335 : /* fd_tile_test_ctx_t contains both common testing infrastructure and
336 : tile-specific state. This structure is updated and checked during
337 : each test loop. The select_in_link function can set the in_link
338 : field to the selected input/upstream/producer link for a test loop,
339 : and NULL if no link selected. The select_out_link function can set
340 : the out_link field to the selected output/downstream/consumer
341 : link for a test loop, and NULL if no link selected. The
342 : tile_specific_struct should be defined in the tile unit test file
343 : and contains any fields local to the tested tile. */
344 : typedef struct fd_tile_test_locals fd_tile_test_locals_t;
345 : struct fd_tile_test_context {
346 : ulong loop_i; // test loop counter
347 :
348 : void (*select_in_link) (fd_tile_test_link_t ** test_links, fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE *); // for selecting upstream producer
349 : void (*select_out_links) (fd_tile_test_link_t ** test_links, fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE *); // for selecting downstream consumers
350 :
351 : fd_tile_test_link_t * in_link; // input link for current loop, set by select_in_link above.
352 :
353 : ulong is_overrun; // whether the current frag is overrun
354 :
355 : int filter; // filter returned by before_frag(...)
356 : int filter_exp; // expected before_frag filter.
357 : // Initialized to 0. Usually set by the publish callback.
358 :
359 : fd_tile_test_locals_t locals[ 1 ]; // Tile specific test fields
360 : };
361 :
362 : /* Initialize an output/consumer/downstream test_link:
363 : Find the link in the topology according link_name, and initialize
364 : the test link with callbacks for frag verification. Usually, the
365 : link was added to topology by fd_topob_tile_out. The check
366 : functions should return a non-zero error code when fail and log
367 : warning. */
368 : void
369 : fd_tile_test_init_link_out( fd_topo_t * topo,
370 : fd_tile_test_link_t * test_link,
371 : const char * link_name,
372 : int (*output_verifier) ( fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE *, fd_tile_test_link_t * ) );
373 :
374 : /* Initialize an input/producer/upstream test_link:
375 : Find the link in the topology according link_name, and initialize
376 : the test link with callbacks for frags generation and verification,
377 : and for checking the tile's context when consuming a frag. Usually
378 : the link was added to topology thourgh fd_topob_tile_in.
379 : find_in_idx is a callback to find the index of this link in tile's
380 : ctx, and cannot be NULL. The check functions should return a
381 : non-zero error code when fail and log warning. */
382 : void
383 : fd_tile_test_init_link_in( fd_topo_t * topo,
384 : fd_tile_test_link_t * test_link,
385 : const char * link_name,
386 : TEST_TILE_CTX_TYPE * ctx,
387 : void (*find_in_idx)( fd_tile_test_link_t *, TEST_TILE_CTX_TYPE * ),
388 : ulong (*publish) ( fd_tile_test_ctx_t *, fd_tile_test_link_t * ),
389 : ulong (*make_sig)( fd_tile_test_ctx_t *, fd_tile_test_link_t * ),
390 : int (*before_frag_check)( fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE * ),
391 : int (*during_frag_check)( fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE * ),
392 : int (*after_frag_check) ( fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE * ) );
393 :
394 :
395 : /* Update one of the input test link's callback. Only one of
396 : pub_or_sig and check will be used, depending on the
397 : callback_fn_num (must be one of test_callback_fn_num above) */
398 : void
399 : fd_tile_test_update_callback_link_in( fd_tile_test_link_t * test_link,
400 : int callback_fn_num,
401 : ulong (*pub_or_sig)( fd_tile_test_ctx_t *, fd_tile_test_link_t * ),
402 : int (*check)( fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE * ) );
403 :
404 : /* Update one of the output test link's callback. callback_fn_num
405 : must be FD_TILE_TEST_CALLBACK_OUTVER. */
406 : void
407 : fd_tile_test_update_callback_link_out( fd_tile_test_link_t * test_link,
408 : int callback_fn_num,
409 : int (*output_verifier)( fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE *, fd_tile_test_link_t * ) );
410 :
411 :
412 : /* Used in select_output_links callback when the tested tile is
413 : expected to produce frags after a tile callback. The
414 : callback_fn_num must be one of FD_TILE_TEST_CALLBACK_BEFORE_CREDIT,
415 : FD_TILE_TEST_CALLBACK_AFTER_CREDIT, and
416 : FD_TILE_TEST_CALLBACK_AFTER_FRAG. The fd_tile_test_run will invoke
417 : the output_verifier callback defined in the test_link after the
418 : specified tile callback to verify output. */
419 : void
420 : fd_tile_test_check_output( int callback_fn_num,
421 : fd_tile_test_link_t * test_link );
422 :
423 : /* Reset test environment between test runs (clears state, resets
424 : input/output selection callbacks, etc ). Must be called before
425 : each test run. */
426 : void
427 : fd_tile_test_reset_env( fd_tile_test_ctx_t * test_ctx,
428 : fd_stem_context_t * stem,
429 : fd_tile_test_link_t ** test_links,
430 : void (*select_in_link) ( fd_tile_test_link_t **, fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE * ),
431 : void (*select_out_links)( fd_tile_test_link_t **, fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE * ),
432 : int (*before_credit_check)( fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE * ),
433 : int (*after_credit_check) ( fd_tile_test_ctx_t *, TEST_TILE_CTX_TYPE * ) );
434 :
435 : /* Main test execution function - runs the tile test through multiple
436 : iterations determined by loop_cnt. For every
437 : 'housekeeping_interval' loops, housekeeping will be ran, including
438 : the very first loop.
439 :
440 : For each iteration:
441 : Select input/upstream/producer link from test_links by callback
442 : test_ctx->select_in_link, which should set test_ctx->in_link.
443 : If an input/upstream/producer link is selected (test_ctx->in_link!=NULL):
444 : - publish data by callback to test_ctx->in_link->publish(...),
445 : - make signature by callback to test_ctx->in_link->make_sig(...)
446 : - Increment the prod_seq by 1, and test_ctx->in_link->chunk by
447 : calling fd_dcache_compact_next
448 : - update the test callbacks that check the tile's frag callbacks.
449 : Select output/downstream/consumer links from test_links by callback
450 : test_ctx->select_out_links.
451 : Execute housekeeping according to 'housekeeping_interval'.
452 : Execute the before_credit and after_credit tile callbacks if defined.
453 : Run verification callback after tile callback to before/after_credit.
454 : If input/upstream/producer link does not publish a frag, continue
455 : to next loop.
456 : Increment the input/upstream/producer link's cons_seq before
457 : calling the first defined tile frag callback.
458 : Run overrun detection between calls to before/during_frag,
459 : and between during/after_frag.
460 : If overrun is detected:
461 : - set input/upstream/producer link's cons_seq to prod_seq-1
462 : - skip the tile's next frag callback and continue to next loop
463 : Run verification callback after each tile's frag callback.
464 : Execute output verifier callbacks if defined.
465 :
466 : Note that under a single-threaded environment, we cannot properly
467 : test overrun. But it's possible to semi-mock the overrun behavior
468 : by incresing the prod_seq in an upstream link by more than depth
469 : amount in the publish callback while only publish one frag to the
470 : tested tile. This will cause the detect_overrun to set the
471 : test_ctx->is_overrun flag, set cons_seq to prod_seq-1 and continue
472 : to the next loop, skipping the next frag callback. We can then
473 : verify whether the overrun frag can been properly cleaned up in the
474 : next test iteration. */
475 :
476 : void
477 : fd_tile_test_run( TEST_TILE_CTX_TYPE * ctx,
478 : fd_stem_context_t * stem,
479 : fd_tile_test_link_t ** test_links,
480 : fd_tile_test_ctx_t * test_ctx,
481 : ulong loop_cnt,
482 : ulong housekeeping_interval );
483 :
484 : #endif /* TEST_TILE_CTX_TYPE */
485 :
486 : #endif /* HEADER_fd_src_app_shared_fd_tile_unit_test_h */
|