]>
Commit | Line | Data |
---|---|---|
9b0a0ba9 OZ |
1 | /* |
2 | * BIRD -- Unit Test Framework (BIRD Test) | |
3 | * | |
4 | * Can be freely distributed and used under the terms of the GNU GPL. | |
5 | */ | |
6 | ||
d6eea6ca | 7 | #include <inttypes.h> |
9b0a0ba9 OZ |
8 | #include <stdarg.h> |
9 | #include <stdint.h> | |
10 | #include <stdio.h> | |
11 | #include <stdlib.h> | |
12 | #include <string.h> | |
13 | #include <signal.h> | |
d6eea6ca | 14 | #include <time.h> |
9b0a0ba9 OZ |
15 | #include <unistd.h> |
16 | ||
17 | #include <sys/ioctl.h> | |
18 | #include <sys/resource.h> | |
19 | #include <sys/wait.h> | |
20 | ||
21 | #include "test/birdtest.h" | |
22 | #include "lib/string.h" | |
9e60a1fb | 23 | #include "lib/event.h" |
9b0a0ba9 OZ |
24 | |
25 | #ifdef HAVE_EXECINFO_H | |
26 | #include <execinfo.h> | |
27 | #endif | |
28 | ||
29 | #define BACKTRACE_MAX_LINES 100 | |
30 | ||
31 | #define sprintf_concat(s, format, ...) \ | |
32 | snprintf(s + strlen(s), sizeof(s) - strlen(s), format, ##__VA_ARGS__) | |
33 | ||
34 | static const char *request; | |
35 | static int list_tests; | |
36 | static int do_core; | |
d348a916 | 37 | static int do_die; |
9b0a0ba9 OZ |
38 | static int no_fork; |
39 | static int no_timeout; | |
40 | static int is_terminal; /* Whether stdout is a live terminal or pipe redirect */ | |
41 | ||
24493e91 MM |
42 | volatile sig_atomic_t async_config_flag; /* Asynchronous reconfiguration/dump scheduled */ |
43 | volatile sig_atomic_t async_dump_flag; | |
44 | volatile sig_atomic_t async_shutdown_flag; | |
45 | ||
46 | ||
9b0a0ba9 OZ |
47 | uint bt_verbose; |
48 | const char *bt_filename; | |
49 | const char *bt_test_id; | |
50 | ||
51 | int bt_result; /* Overall program run result */ | |
52 | int bt_suite_result; /* One suit result */ | |
53 | char bt_out_fmt_buf[1024]; /* Temporary memory buffer for output of testing function */ | |
54 | ||
d6eea6ca MM |
55 | struct timespec bt_begin, bt_suite_begin, bt_suite_case_begin; |
56 | ||
bb001af0 MM |
57 | u64 bt_random_state[] = { |
58 | 0x80241f302bd4d95d, 0xd10ba2e910f772b, 0xea188c9046f507c5, 0x4c4c581f04e6da05, | |
59 | 0x53d9772877c1b647, 0xab8ce3eb466de6c5, 0xad02844c8a8e865f, 0xe8cc78080295065d | |
60 | }; | |
9b0a0ba9 OZ |
61 | |
62 | void | |
63 | bt_init(int argc, char *argv[]) | |
64 | { | |
65 | int c; | |
66 | ||
bb001af0 | 67 | initstate(BT_RANDOM_SEED, (char *) bt_random_state, sizeof(bt_random_state)); |
9b0a0ba9 OZ |
68 | |
69 | bt_verbose = 0; | |
70 | bt_filename = argv[0]; | |
5e3cd0e5 | 71 | bt_result = 1; |
9b0a0ba9 OZ |
72 | bt_test_id = NULL; |
73 | is_terminal = isatty(fileno(stdout)); | |
74 | ||
d348a916 | 75 | while ((c = getopt(argc, argv, "lcdftv")) >= 0) |
9b0a0ba9 OZ |
76 | switch (c) |
77 | { | |
78 | case 'l': | |
79 | list_tests = 1; | |
80 | break; | |
81 | ||
82 | case 'c': | |
83 | do_core = 1; | |
84 | break; | |
85 | ||
d348a916 MM |
86 | case 'd': |
87 | do_die = 1; | |
88 | break; | |
89 | ||
9b0a0ba9 OZ |
90 | case 'f': |
91 | no_fork = 1; | |
92 | break; | |
93 | ||
94 | case 't': | |
95 | no_timeout = 1; | |
96 | break; | |
97 | ||
98 | case 'v': | |
99 | bt_verbose++; | |
100 | break; | |
101 | ||
102 | default: | |
103 | goto usage; | |
104 | } | |
105 | ||
106 | /* Optional requested test_id */ | |
107 | if ((optind + 1) == argc) | |
108 | request = argv[optind++]; | |
109 | ||
110 | if (optind != argc) | |
111 | goto usage; | |
112 | ||
113 | if (do_core) | |
114 | { | |
115 | struct rlimit rl = {RLIM_INFINITY, RLIM_INFINITY}; | |
116 | int rv = setrlimit(RLIMIT_CORE, &rl); | |
117 | bt_syscall(rv < 0, "setrlimit RLIMIT_CORE"); | |
118 | } | |
119 | ||
d6eea6ca | 120 | clock_gettime(CLOCK_MONOTONIC, &bt_begin); |
3dabf7b8 | 121 | bt_suite_case_begin = bt_suite_begin = bt_begin; |
d6eea6ca | 122 | |
48bf1322 | 123 | resource_init(); |
9e60a1fb | 124 | ev_init_list(&global_event_list); |
48bf1322 | 125 | |
9b0a0ba9 OZ |
126 | return; |
127 | ||
128 | usage: | |
d348a916 | 129 | printf("Usage: %s [-l] [-c] [-d] [-f] [-t] [-vvv] [<test_suit_name>]\n", argv[0]); |
9b0a0ba9 OZ |
130 | printf("Options: \n"); |
131 | printf(" -l List all test suite names and descriptions \n"); | |
132 | printf(" -c Force unlimit core dumps (needs root privileges) \n"); | |
d348a916 | 133 | printf(" -d Die on first failed test case \n"); |
9b0a0ba9 OZ |
134 | printf(" -f No forking \n"); |
135 | printf(" -t No timeout limit \n"); | |
136 | printf(" -v More verbosity, maximum is 3 -vvv \n"); | |
137 | exit(3); | |
138 | } | |
139 | ||
140 | static void | |
141 | bt_dump_backtrace(void) | |
142 | { | |
143 | #ifdef HAVE_EXECINFO_H | |
144 | void *buf[BACKTRACE_MAX_LINES]; | |
145 | char **pp_backtrace; | |
146 | int lines, j; | |
147 | ||
148 | if (!bt_verbose) | |
149 | return; | |
150 | ||
151 | lines = backtrace(buf, BACKTRACE_MAX_LINES); | |
152 | bt_log("backtrace() returned %d addresses", lines); | |
153 | ||
154 | pp_backtrace = backtrace_symbols(buf, lines); | |
155 | if (pp_backtrace == NULL) | |
156 | { | |
157 | perror("backtrace_symbols"); | |
158 | exit(EXIT_FAILURE); | |
159 | } | |
160 | ||
161 | for (j = 0; j < lines; j++) | |
162 | bt_log("%s", pp_backtrace[j]); | |
163 | ||
164 | free(pp_backtrace); | |
165 | #endif /* HAVE_EXECINFO_H */ | |
166 | } | |
167 | ||
168 | static | |
169 | int bt_run_test_fn(int (*fn)(const void *), const void *fn_arg, int timeout) | |
170 | { | |
171 | int result; | |
172 | alarm(timeout); | |
173 | ||
0a793ebc | 174 | result = fn(fn_arg); |
9b0a0ba9 | 175 | |
5e3cd0e5 PT |
176 | if (!bt_suite_result) |
177 | result = 0; | |
9b0a0ba9 | 178 | |
48bf1322 MM |
179 | tmp_flush(); |
180 | ||
9b0a0ba9 OZ |
181 | return result; |
182 | } | |
183 | ||
184 | static uint | |
185 | get_num_terminal_cols(void) | |
186 | { | |
187 | struct winsize w = {}; | |
188 | ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); | |
189 | uint cols = w.ws_col; | |
190 | return (cols > 0 ? cols : 80); | |
191 | } | |
192 | ||
193 | /** | |
194 | * bt_log_result - pretty print of test result | |
5e3cd0e5 | 195 | * @result: 1 or 0 |
9b0a0ba9 OZ |
196 | * @fmt: a description message (could be long, over more lines) |
197 | * @argptr: variable argument list | |
198 | * | |
199 | * This function is used for pretty printing of test results on all verbose | |
200 | * levels. | |
201 | */ | |
202 | static void | |
d6eea6ca | 203 | bt_log_result(int result, u64 time, const char *fmt, va_list argptr) |
9b0a0ba9 | 204 | { |
fa71b268 | 205 | static char msg_buf[BT_BUFFER_SIZE]; |
9b0a0ba9 OZ |
206 | char *pos; |
207 | ||
3dabf7b8 | 208 | snprintf(msg_buf, sizeof(msg_buf), "%s%s%s %" PRIu64 ".%09" PRIu64 "s%s", |
9b0a0ba9 OZ |
209 | bt_filename, |
210 | bt_test_id ? ": " : "", | |
211 | bt_test_id ? bt_test_id : "", | |
d6eea6ca | 212 | time / 1000000000, |
3dabf7b8 OZ |
213 | time % 1000000000, |
214 | (fmt && strlen(fmt) > 0) ? ": " : ""); | |
9b0a0ba9 OZ |
215 | pos = msg_buf + strlen(msg_buf); |
216 | ||
c41a914d MM |
217 | if (fmt) |
218 | vsnprintf(pos, sizeof(msg_buf) - (pos - msg_buf), fmt, argptr); | |
9b0a0ba9 | 219 | |
fa71b268 | 220 | int chrs = 0; |
eeba61cc | 221 | for (uint i = 0; i < strlen(msg_buf); i += get_num_terminal_cols()) |
fa71b268 PT |
222 | { |
223 | if (i) | |
224 | printf("\n"); | |
225 | char *stop = msg_buf + i + get_num_terminal_cols(); | |
226 | char backup = *stop; | |
227 | *stop = 0; | |
228 | chrs = printf("%s", msg_buf + i); | |
229 | *stop = backup; | |
230 | } | |
231 | ||
232 | int offset = get_num_terminal_cols() - chrs - BT_PROMPT_OK_FAIL_STRLEN; | |
233 | if (offset < 0) | |
234 | { | |
235 | printf("\n"); | |
236 | offset = get_num_terminal_cols() - BT_PROMPT_OK_FAIL_STRLEN; | |
237 | } | |
238 | ||
239 | for (int i = 0; i < offset; i++) | |
240 | putchar(' '); | |
9b0a0ba9 OZ |
241 | |
242 | const char *result_str = is_terminal ? BT_PROMPT_OK : BT_PROMPT_OK_NO_COLOR; | |
5e3cd0e5 | 243 | if (!result) |
9b0a0ba9 OZ |
244 | result_str = is_terminal ? BT_PROMPT_FAIL : BT_PROMPT_FAIL_NO_COLOR; |
245 | ||
fa71b268 | 246 | printf("%s\n", result_str); |
d348a916 MM |
247 | |
248 | if (do_die && !result) | |
c73343de | 249 | abort(); |
9b0a0ba9 OZ |
250 | } |
251 | ||
d6eea6ca MM |
252 | static u64 |
253 | get_time_diff(struct timespec *begin) | |
254 | { | |
255 | struct timespec end; | |
256 | clock_gettime(CLOCK_MONOTONIC, &end); | |
257 | return (end.tv_sec - begin->tv_sec) * 1000000000ULL | |
258 | + end.tv_nsec - begin->tv_nsec; | |
259 | } | |
260 | ||
9b0a0ba9 OZ |
261 | /** |
262 | * bt_log_overall_result - pretty print of suite case result | |
5e3cd0e5 | 263 | * @result: 1 or 0 |
9b0a0ba9 OZ |
264 | * @fmt: a description message (could be long, over more lines) |
265 | * ...: variable argument list | |
266 | * | |
267 | * This function is used for pretty printing of test suite case result. | |
268 | */ | |
269 | static void | |
270 | bt_log_overall_result(int result, const char *fmt, ...) | |
271 | { | |
272 | va_list argptr; | |
273 | va_start(argptr, fmt); | |
d6eea6ca | 274 | bt_log_result(result, get_time_diff(&bt_begin), fmt, argptr); |
9b0a0ba9 OZ |
275 | va_end(argptr); |
276 | } | |
277 | ||
278 | /** | |
279 | * bt_log_suite_result - pretty print of suite case result | |
5e3cd0e5 | 280 | * @result: 1 or 0 |
9b0a0ba9 OZ |
281 | * @fmt: a description message (could be long, over more lines) |
282 | * ...: variable argument list | |
283 | * | |
284 | * This function is used for pretty printing of test suite case result. | |
285 | */ | |
286 | void | |
287 | bt_log_suite_result(int result, const char *fmt, ...) | |
288 | { | |
d6eea6ca | 289 | if (bt_verbose >= BT_VERBOSE_SUITE || !result) |
9b0a0ba9 OZ |
290 | { |
291 | va_list argptr; | |
292 | va_start(argptr, fmt); | |
d6eea6ca | 293 | bt_log_result(result, get_time_diff(&bt_suite_begin), fmt, argptr); |
9b0a0ba9 OZ |
294 | va_end(argptr); |
295 | } | |
296 | } | |
297 | ||
298 | /** | |
299 | * bt_log_suite_case_result - pretty print of suite result | |
5e3cd0e5 | 300 | * @result: 1 or 0 |
9b0a0ba9 OZ |
301 | * @fmt: a description message (could be long, over more lines) |
302 | * ...: variable argument list | |
303 | * | |
304 | * This function is used for pretty printing of test suite result. | |
305 | */ | |
306 | void | |
307 | bt_log_suite_case_result(int result, const char *fmt, ...) | |
308 | { | |
309 | if(bt_verbose >= BT_VERBOSE_SUITE_CASE) | |
310 | { | |
311 | va_list argptr; | |
312 | va_start(argptr, fmt); | |
d6eea6ca | 313 | bt_log_result(result, get_time_diff(&bt_suite_case_begin), fmt, argptr); |
9b0a0ba9 OZ |
314 | va_end(argptr); |
315 | } | |
316 | } | |
317 | ||
067f69a5 OZ |
318 | void |
319 | bt_reset_suite_case_timer(void) | |
320 | { | |
321 | clock_gettime(CLOCK_MONOTONIC, &bt_suite_case_begin); | |
322 | } | |
323 | ||
9b0a0ba9 OZ |
324 | int |
325 | bt_test_suite_base(int (*fn)(const void *), const char *id, const void *fn_arg, int forked, int timeout, const char *dsc, ...) | |
326 | { | |
327 | if (list_tests) | |
328 | { | |
329 | printf("%28s - ", id); | |
330 | va_list args; | |
331 | va_start(args, dsc); | |
332 | vprintf(dsc, args); | |
333 | va_end(args); | |
334 | printf("\n"); | |
5e3cd0e5 | 335 | return 1; |
9b0a0ba9 OZ |
336 | } |
337 | ||
338 | if (no_fork) | |
339 | forked = 0; | |
340 | ||
341 | if (no_timeout) | |
342 | timeout = 0; | |
343 | ||
344 | if (request && strcmp(id, request)) | |
5e3cd0e5 | 345 | return 1; |
9b0a0ba9 | 346 | |
5e3cd0e5 | 347 | bt_suite_result = 1; |
9b0a0ba9 OZ |
348 | bt_test_id = id; |
349 | ||
350 | if (bt_verbose >= BT_VERBOSE_ABSOLUTELY_ALL) | |
351 | bt_log("Starting"); | |
352 | ||
d6eea6ca | 353 | clock_gettime(CLOCK_MONOTONIC, &bt_suite_begin); |
3dabf7b8 | 354 | bt_suite_case_begin = bt_suite_begin; |
d6eea6ca | 355 | |
9b0a0ba9 OZ |
356 | if (!forked) |
357 | { | |
358 | bt_suite_result = bt_run_test_fn(fn, fn_arg, timeout); | |
359 | } | |
360 | else | |
361 | { | |
362 | pid_t pid = fork(); | |
363 | bt_syscall(pid < 0, "fork"); | |
364 | ||
365 | if (pid == 0) | |
366 | { | |
367 | /* child of fork */ | |
368 | _exit(bt_run_test_fn(fn, fn_arg, timeout)); | |
369 | } | |
370 | ||
371 | int s; | |
372 | int rv = waitpid(pid, &s, 0); | |
373 | bt_syscall(rv < 0, "waitpid"); | |
374 | ||
375 | if (WIFEXITED(s)) | |
376 | { | |
377 | /* Normal exit */ | |
378 | bt_suite_result = WEXITSTATUS(s); | |
379 | } | |
380 | else if (WIFSIGNALED(s)) | |
381 | { | |
382 | /* Stopped by signal */ | |
5e3cd0e5 | 383 | bt_suite_result = 0; |
9b0a0ba9 OZ |
384 | |
385 | int sn = WTERMSIG(s); | |
386 | if (sn == SIGALRM) | |
387 | { | |
388 | bt_log("Timeout expired"); | |
389 | } | |
390 | else if (sn == SIGSEGV) | |
391 | { | |
392 | bt_log("Segmentation fault"); | |
393 | bt_dump_backtrace(); | |
394 | } | |
395 | else if (sn != SIGABRT) | |
396 | bt_log("Signal %d received", sn); | |
397 | } | |
398 | ||
399 | if (WCOREDUMP(s) && bt_verbose) | |
400 | bt_log("Core dumped"); | |
401 | } | |
402 | ||
5e3cd0e5 PT |
403 | if (!bt_suite_result) |
404 | bt_result = 0; | |
9b0a0ba9 OZ |
405 | |
406 | bt_log_suite_result(bt_suite_result, NULL); | |
407 | bt_test_id = NULL; | |
408 | ||
409 | return bt_suite_result; | |
410 | } | |
411 | ||
412 | int | |
413 | bt_exit_value(void) | |
414 | { | |
5e3cd0e5 | 415 | if (!list_tests || (list_tests && !bt_result)) |
9b0a0ba9 | 416 | bt_log_overall_result(bt_result, ""); |
5e3cd0e5 | 417 | return bt_result ? EXIT_SUCCESS : EXIT_FAILURE; |
9b0a0ba9 OZ |
418 | } |
419 | ||
420 | /** | |
421 | * bt_assert_batch__ - test a batch of inputs/outputs tests | |
422 | * @opts: includes all necessary data | |
423 | * | |
424 | * Should be called using macro bt_assert_batch(). | |
5e3cd0e5 | 425 | * Returns 1 or 0. |
9b0a0ba9 OZ |
426 | */ |
427 | int | |
428 | bt_assert_batch__(struct bt_batch *opts) | |
429 | { | |
430 | int i; | |
431 | for (i = 0; i < opts->ndata; i++) | |
432 | { | |
d6eea6ca MM |
433 | if (bt_verbose >= BT_VERBOSE_SUITE) |
434 | clock_gettime(CLOCK_MONOTONIC, &bt_suite_case_begin); | |
435 | ||
9b0a0ba9 OZ |
436 | int bt_suit_case_result = opts->test_fn(opts->out_buf, opts->data[i].in, opts->data[i].out); |
437 | ||
5e3cd0e5 PT |
438 | if (bt_suit_case_result == 0) |
439 | bt_suite_result = 0; | |
9b0a0ba9 OZ |
440 | |
441 | char b[BT_BUFFER_SIZE]; | |
442 | snprintf(b, sizeof(b), "%s(", opts->test_fn_name); | |
443 | ||
444 | opts->in_fmt(b+strlen(b), sizeof(b)-strlen(b), opts->data[i].in); | |
445 | sprintf_concat(b, ") gives "); | |
446 | opts->out_fmt(b+strlen(b), sizeof(b)-strlen(b), opts->out_buf); | |
447 | ||
5e3cd0e5 | 448 | if (bt_suit_case_result == 0) |
9b0a0ba9 OZ |
449 | { |
450 | sprintf_concat(b, ", but expecting is "); | |
451 | opts->out_fmt(b+strlen(b), sizeof(b)-strlen(b), opts->data[i].out); | |
452 | } | |
453 | ||
454 | bt_log_suite_case_result(bt_suit_case_result, "%s", b); | |
455 | } | |
456 | ||
457 | return bt_suite_result; | |
458 | } | |
459 | ||
460 | /** | |
461 | * bt_fmt_str - formating string into output buffer | |
462 | * @buf: buffer for write | |
463 | * @size: empty size in @buf | |
464 | * @data: null-byte terminated string | |
465 | * | |
466 | * This function can be used with bt_assert_batch() function. | |
467 | * Input @data should be const char * string. | |
468 | */ | |
469 | void | |
470 | bt_fmt_str(char *buf, size_t size, const void *data) | |
471 | { | |
472 | const byte *s = data; | |
473 | ||
474 | snprintf(buf, size, "\""); | |
475 | while (*s) | |
476 | { | |
477 | snprintf(buf+strlen(buf), size-strlen(buf), bt_is_char(*s) ? "%c" : "\\%03u", *s); | |
478 | s++; | |
479 | } | |
480 | snprintf(buf+strlen(buf), size-strlen(buf), "\""); | |
481 | } | |
482 | ||
483 | /** | |
484 | * bt_fmt_unsigned - formating unsigned int into output buffer | |
485 | * @buf: buffer for write | |
486 | * @size: empty size in @buf | |
487 | * @data: unsigned number | |
488 | * | |
489 | * This function can be used with bt_assert_batch() function. | |
490 | */ | |
491 | void | |
492 | bt_fmt_unsigned(char *buf, size_t size, const void *data) | |
493 | { | |
494 | const uint *n = data; | |
495 | snprintf(buf, size, "0x%x (%u)", *n, *n); | |
496 | } | |
497 | ||
498 | /** | |
499 | * bt_fmt_ipa - formating ip_addr into output buffer | |
500 | * @buf: buffer for write | |
501 | * @size: empty size in @buf | |
502 | * @data: should be struct ip_addr * | |
503 | * | |
504 | * This function can be used with bt_assert_batch() function. | |
505 | */ | |
506 | void | |
507 | bt_fmt_ipa(char *buf, size_t size, const void *data) | |
508 | { | |
509 | const ip_addr *ip = data; | |
a1b61a27 MM |
510 | if (data) |
511 | bsnprintf(buf, size, "%I", *ip); | |
512 | else | |
513 | bsnprintf(buf, size, "(null)"); | |
9b0a0ba9 OZ |
514 | } |
515 | ||
e709dc09 OZ |
516 | void |
517 | bt_format_net(char *buf, size_t size, const void *data) | |
518 | { | |
14fc24f3 OZ |
519 | if (data) |
520 | bsnprintf(buf, size, "%N", (const net_addr *) data); | |
521 | else | |
522 | bsnprintf(buf, size, "(null)"); | |
e709dc09 OZ |
523 | } |
524 | ||
9b0a0ba9 OZ |
525 | int |
526 | bt_is_char(byte c) | |
527 | { | |
528 | return (c >= (byte) 32 && c <= (byte) 126); | |
529 | } | |
530 | ||
531 | /* | |
532 | * Mock-ups of all necessary public functions in main.c | |
533 | */ | |
534 | ||
63451c19 | 535 | int parse_and_exit; |
9b0a0ba9 OZ |
536 | char *bird_name; |
537 | void async_config(void) {} | |
538 | void async_dump(void) {} | |
539 | void async_shutdown(void) {} | |
77ce849e | 540 | char *get_hostname(linpool *lp UNUSED) { return NULL; } |
9b0a0ba9 OZ |
541 | void cmd_check_config(char *name UNUSED) {} |
542 | void cmd_reconfig(char *name UNUSED, int type UNUSED, int timeout UNUSED) {} | |
543 | void cmd_reconfig_confirm(void) {} | |
544 | void cmd_reconfig_undo(void) {} | |
bdf2e55d | 545 | void cmd_reconfig_status(void) {} |
bb57d917 | 546 | void cmd_graceful_restart(void) {} |
9b0a0ba9 OZ |
547 | void cmd_shutdown(void) {} |
548 | void cmd_reconfig_undo_notify(void) {} | |
549 | ||
550 | #include "nest/bird.h" | |
551 | #include "lib/net.h" | |
552 | #include "conf/conf.h" | |
553 | void sysdep_preconfig(struct config *c UNUSED) {} | |
554 | int sysdep_commit(struct config *new UNUSED, struct config *old UNUSED) { return 0; } | |
555 | void sysdep_shutdown_done(void) {} | |
556 | ||
557 | #include "nest/cli.h" | |
558 | int cli_get_command(cli *c UNUSED) { return 0; } | |
559 | void cli_write_trigger(cli *c UNUSED) {} | |
560 | cli *cmd_reconfig_stored_cli; |