2 * testcode/replay.c - store and use a replay of events for the DNS resolver.
4 * Copyright (c) 2007, NLnet Labs. All rights reserved.
6 * This software is open source.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
15 * Redistributions in binary form must reproduce the above copyright notice,
16 * this list of conditions and the following disclaimer in the documentation
17 * and/or other materials provided with the distribution.
19 * Neither the name of the NLNET LABS nor the names of its contributors may
20 * be used to endorse or promote products derived from this software without
21 * specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 * Store and use a replay of events for the DNS resolver.
39 * Used to test known scenarios to get known outcomes.
43 /* for strtod prototype */
48 #include "util/net_help.h"
49 #include "util/config_file.h"
50 #include "testcode/replay.h"
51 #include "testcode/testpkts.h"
52 #include "testcode/fake_event.h"
53 #include "sldns/str2wire.h"
55 /** max length of lines in file */
56 #define MAX_LINE_LEN 10240
60 * @param store: value storage
61 * @param runtime: replay runtime for other stuff.
62 * @param text: the macro text, after the ${, Updated to after the } when
63 * done (successfully).
64 * @return expanded text, malloced. NULL on failure.
66 static char* macro_expand(rbtree_type* store,
67 struct replay_runtime* runtime, char** text);
69 /** compare of time values */
71 timeval_smaller(const struct timeval* x, const struct timeval* y)
74 if(x->tv_sec < y->tv_sec)
76 else if(x->tv_sec == y->tv_sec) {
77 if(x->tv_usec <= y->tv_usec)
85 /** parse keyword in string.
86 * @param line: if found, the line is advanced to after the keyword.
87 * @param keyword: string.
88 * @return: true if found, false if not.
91 parse_keyword(char** line, const char* keyword)
93 size_t len = (size_t)strlen(keyword);
94 if(strncmp(*line, keyword, len) == 0) {
103 replay_moment_delete(struct replay_moment* mom)
108 delete_entry(mom->match);
110 free(mom->autotrust_id);
113 config_delstrlist(mom->file_content);
119 replay_range_delete(struct replay_range* rng)
123 delete_entry(rng->match);
127 /** strip whitespace from end of string */
129 strip_end_white(char* p)
132 for(i = strlen(p); i > 0; i--) {
133 if(isspace((unsigned char)p[i-1]))
140 * Read a range from file.
141 * @param remain: Rest of line (after RANGE keyword).
142 * @param in: file to read from.
143 * @param name: name to print in errors.
144 * @param pstate: read state structure with
145 * with lineno : incremented as lines are read.
146 * ttl, origin, prev for readentry.
147 * @param line: line buffer.
148 * @return: range object to add to list, or NULL on error.
150 static struct replay_range*
151 replay_range_read(char* remain, FILE* in, const char* name,
152 struct sldns_file_parse_state* pstate, char* line)
154 struct replay_range* rng = (struct replay_range*)malloc(
155 sizeof(struct replay_range));
158 struct entry* entry, *last = NULL;
161 memset(rng, 0, sizeof(*rng));
162 /* read time range */
163 if(sscanf(remain, " %d %d", &rng->start_step, &rng->end_step)!=2) {
164 log_err("Could not read time range: %s", line);
170 while(fgets(line, MAX_LINE_LEN-1, in)) {
173 while(isspace((unsigned char)*parse))
175 if(!*parse || *parse == ';') {
179 if(parse_keyword(&parse, "ADDRESS")) {
180 while(isspace((unsigned char)*parse))
182 strip_end_white(parse);
183 if(!extstrtoaddr(parse, &rng->addr, &rng->addrlen)) {
184 log_err("Line %d: could not read ADDRESS: %s",
185 pstate->lineno, parse);
192 if(parse_keyword(&parse, "RANGE_END")) {
195 /* set position before line; read entry */
197 fseeko(in, pos, SEEK_SET);
198 entry = read_entry(in, name, pstate, 1);
200 fatal_exit("%d: bad entry", pstate->lineno);
204 else rng->match = entry;
209 replay_range_delete(rng);
213 /** Read FILE match content */
215 read_file_content(FILE* in, int* lineno, struct replay_moment* mom)
217 char line[MAX_LINE_LEN];
219 struct config_strlist** last = &mom->file_content;
220 line[MAX_LINE_LEN-1]=0;
221 if(!fgets(line, MAX_LINE_LEN-1, in))
222 fatal_exit("FILE_BEGIN expected at line %d", *lineno);
223 if(!parse_keyword(&remain, "FILE_BEGIN"))
224 fatal_exit("FILE_BEGIN expected at line %d", *lineno);
225 while(fgets(line, MAX_LINE_LEN-1, in)) {
227 if(strncmp(line, "FILE_END", 8) == 0) {
230 if(line[0]) line[strlen(line)-1] = 0; /* remove newline */
231 if(!cfg_strlist_insert(last, strdup(line)))
232 fatal_exit("malloc failure");
233 last = &( (*last)->next );
235 fatal_exit("no FILE_END in input file");
238 /** read assign step info */
240 read_assign_step(char* remain, struct replay_moment* mom)
245 buf[sizeof(buf)-1]=0;
246 if(sscanf(remain, " %1023s %c %n", buf, &eq, &skip) != 2)
247 fatal_exit("cannot parse assign: %s", remain);
248 mom->variable = strdup(buf);
250 fatal_exit("no '=' in assign: %s", remain);
252 if(remain[0]) remain[strlen(remain)-1]=0; /* remove newline */
253 mom->string = strdup(remain);
254 if(!mom->variable || !mom->string)
255 fatal_exit("out of memory");
259 * Read a replay moment 'STEP' from file.
260 * @param remain: Rest of line (after STEP keyword).
261 * @param in: file to read from.
262 * @param name: name to print in errors.
263 * @param pstate: with lineno, ttl, origin, prev for parse state.
264 * lineno is incremented.
265 * @return: range object to add to list, or NULL on error.
267 static struct replay_moment*
268 replay_moment_read(char* remain, FILE* in, const char* name,
269 struct sldns_file_parse_state* pstate)
271 struct replay_moment* mom = (struct replay_moment*)malloc(
272 sizeof(struct replay_moment));
277 memset(mom, 0, sizeof(*mom));
278 if(sscanf(remain, " %d%n", &mom->time_step, &skip) != 1) {
279 log_err("%d: cannot read number: %s", pstate->lineno, remain);
284 while(isspace((unsigned char)*remain))
286 if(parse_keyword(&remain, "NOTHING")) {
287 mom->evt_type = repevt_nothing;
288 } else if(parse_keyword(&remain, "QUERY")) {
289 mom->evt_type = repevt_front_query;
291 if(!extstrtoaddr("127.0.0.1", &mom->addr, &mom->addrlen))
292 fatal_exit("internal error");
293 } else if(parse_keyword(&remain, "CHECK_ANSWER")) {
294 mom->evt_type = repevt_front_reply;
296 } else if(parse_keyword(&remain, "CHECK_OUT_QUERY")) {
297 mom->evt_type = repevt_back_query;
299 } else if(parse_keyword(&remain, "REPLY")) {
300 mom->evt_type = repevt_back_reply;
302 } else if(parse_keyword(&remain, "TIMEOUT")) {
303 mom->evt_type = repevt_timeout;
304 } else if(parse_keyword(&remain, "TIME_PASSES")) {
305 mom->evt_type = repevt_time_passes;
306 while(isspace((unsigned char)*remain))
308 if(parse_keyword(&remain, "EVAL")) {
309 while(isspace((unsigned char)*remain))
311 mom->string = strdup(remain);
312 if(!mom->string) fatal_exit("out of memory");
313 if(strlen(mom->string)>0)
314 mom->string[strlen(mom->string)-1]=0;
315 remain += strlen(mom->string);
317 } else if(parse_keyword(&remain, "CHECK_AUTOTRUST")) {
318 mom->evt_type = repevt_autotrust_check;
319 while(isspace((unsigned char)*remain))
321 if(strlen(remain)>0 && remain[strlen(remain)-1]=='\n')
322 remain[strlen(remain)-1] = 0;
323 mom->autotrust_id = strdup(remain);
324 if(!mom->autotrust_id) fatal_exit("out of memory");
325 read_file_content(in, &pstate->lineno, mom);
326 } else if(parse_keyword(&remain, "CHECK_TEMPFILE")) {
327 mom->evt_type = repevt_tempfile_check;
328 while(isspace((unsigned char)*remain))
330 if(strlen(remain)>0 && remain[strlen(remain)-1]=='\n')
331 remain[strlen(remain)-1] = 0;
332 mom->autotrust_id = strdup(remain);
333 if(!mom->autotrust_id) fatal_exit("out of memory");
334 read_file_content(in, &pstate->lineno, mom);
335 } else if(parse_keyword(&remain, "ERROR")) {
336 mom->evt_type = repevt_error;
337 } else if(parse_keyword(&remain, "TRAFFIC")) {
338 mom->evt_type = repevt_traffic;
339 } else if(parse_keyword(&remain, "ASSIGN")) {
340 mom->evt_type = repevt_assign;
341 read_assign_step(remain, mom);
342 } else if(parse_keyword(&remain, "INFRA_RTT")) {
344 mom->evt_type = repevt_infra_rtt;
345 while(isspace((unsigned char)*remain))
348 remain = strchr(s, ' ');
349 if(!remain) fatal_exit("expected three args for INFRA_RTT");
352 while(isspace((unsigned char)*remain))
354 m = strchr(remain, ' ');
355 if(!m) fatal_exit("expected three args for INFRA_RTT");
358 while(isspace((unsigned char)*m))
360 if(!extstrtoaddr(s, &mom->addr, &mom->addrlen))
361 fatal_exit("bad infra_rtt address %s", s);
362 if(strlen(m)>0 && m[strlen(m)-1]=='\n')
364 mom->variable = strdup(remain);
365 mom->string = strdup(m);
366 if(!mom->string) fatal_exit("out of memory");
367 if(!mom->variable) fatal_exit("out of memory");
369 log_err("%d: unknown event type %s", pstate->lineno, remain);
373 while(isspace((unsigned char)*remain))
375 if(parse_keyword(&remain, "ADDRESS")) {
376 while(isspace((unsigned char)*remain))
378 if(strlen(remain) > 0) /* remove \n */
379 remain[strlen(remain)-1] = 0;
380 if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen)) {
381 log_err("line %d: could not parse ADDRESS: %s",
382 pstate->lineno, remain);
387 if(parse_keyword(&remain, "ELAPSE")) {
390 sec = strtod(remain, &remain);
391 if(sec == 0. && errno != 0) {
392 log_err("line %d: could not parse ELAPSE: %s (%s)",
393 pstate->lineno, remain, strerror(errno));
398 mom->elapse.tv_sec = (int)sec;
399 mom->elapse.tv_usec = (int)((sec - (double)mom->elapse.tv_sec)
405 mom->match = read_entry(in, name, pstate, 1);
415 /** makes scenario with title on rest of line */
416 static struct replay_scenario*
417 make_scenario(char* line)
419 struct replay_scenario* scen;
420 while(isspace((unsigned char)*line))
423 log_err("scenario: no title given");
426 scen = (struct replay_scenario*)malloc(sizeof(struct replay_scenario));
429 memset(scen, 0, sizeof(*scen));
430 scen->title = strdup(line);
438 struct replay_scenario*
439 replay_scenario_read(FILE* in, const char* name, int* lineno)
441 char line[MAX_LINE_LEN];
443 struct replay_scenario* scen = NULL;
444 struct sldns_file_parse_state pstate;
445 line[MAX_LINE_LEN-1]=0;
446 memset(&pstate, 0, sizeof(pstate));
447 pstate.default_ttl = 3600;
448 pstate.lineno = *lineno;
450 while(fgets(line, MAX_LINE_LEN-1, in)) {
454 while(isspace((unsigned char)*parse))
457 continue; /* empty line */
458 if(parse_keyword(&parse, ";"))
459 continue; /* comment */
460 if(parse_keyword(&parse, "SCENARIO_BEGIN")) {
462 fatal_exit("%d: double SCENARIO_BEGIN", *lineno);
463 scen = make_scenario(parse);
465 fatal_exit("%d: could not make scen", *lineno);
469 fatal_exit("%d: expected SCENARIO", *lineno);
470 if(parse_keyword(&parse, "RANGE_BEGIN")) {
471 struct replay_range* newr = replay_range_read(parse,
472 in, name, &pstate, line);
474 fatal_exit("%d: bad range", pstate.lineno);
475 *lineno = pstate.lineno;
476 newr->next_range = scen->range_list;
477 scen->range_list = newr;
478 } else if(parse_keyword(&parse, "STEP")) {
479 struct replay_moment* mom = replay_moment_read(parse,
482 fatal_exit("%d: bad moment", pstate.lineno);
483 *lineno = pstate.lineno;
485 scen->mom_last->time_step >= mom->time_step)
486 fatal_exit("%d: time goes backwards", *lineno);
488 scen->mom_last->mom_next = mom;
489 else scen->mom_first = mom;
490 scen->mom_last = mom;
491 } else if(parse_keyword(&parse, "SCENARIO_END")) {
492 struct replay_moment *p = scen->mom_first;
498 log_info("Scenario has %d steps", num);
502 log_err("scenario read failed at line %d (no SCENARIO_END?)", *lineno);
503 replay_scenario_delete(scen);
508 replay_scenario_delete(struct replay_scenario* scen)
510 struct replay_moment* mom, *momn;
511 struct replay_range* rng, *rngn;
515 mom = scen->mom_first;
517 momn = mom->mom_next;
518 replay_moment_delete(mom);
521 rng = scen->range_list;
523 rngn = rng->next_range;
524 replay_range_delete(rng);
530 /** fetch oldest timer in list that is enabled */
531 static struct fake_timer*
532 first_timer(struct replay_runtime* runtime)
534 struct fake_timer* p, *res = NULL;
535 for(p=runtime->timer_list; p; p=p->next) {
540 else if(timeval_smaller(&p->tv, &res->tv))
547 replay_get_oldest_timer(struct replay_runtime* runtime)
549 struct fake_timer* t = first_timer(runtime);
550 if(t && timeval_smaller(&t->tv, &runtime->now_tv))
556 replay_var_compare(const void* a, const void* b)
558 struct replay_var* x = (struct replay_var*)a;
559 struct replay_var* y = (struct replay_var*)b;
560 return strcmp(x->name, y->name);
564 macro_store_create(void)
566 return rbtree_create(&replay_var_compare);
569 /** helper function to delete macro values */
571 del_macro(rbnode_type* x, void* ATTR_UNUSED(arg))
573 struct replay_var* v = (struct replay_var*)x;
580 macro_store_delete(rbtree_type* store)
584 traverse_postorder(store, del_macro, NULL);
588 /** return length of macro */
590 macro_length(char* text)
592 /* we are after ${, looking for } */
601 } else if(text[0] == '$' && text[1] == '{') {
609 /** insert new stuff at start of buffer */
611 do_buf_insert(char* buf, size_t remain, char* after, char* inserted)
613 char* save = strdup(after);
616 if(strlen(inserted) > remain) {
620 len = strlcpy(buf, inserted, remain);
623 (void)strlcpy(buf, save, remain);
628 /** do macro recursion */
630 do_macro_recursion(rbtree_type* store, struct replay_runtime* runtime,
631 char* at, size_t remain)
634 char* expand = macro_expand(store, runtime, &after);
636 return NULL; /* expansion failed */
637 if(!do_buf_insert(at, remain, after, expand)) {
642 return at; /* and parse over the expanded text to see if again */
645 /** get var from store */
646 static struct replay_var*
647 macro_getvar(rbtree_type* store, char* name)
652 return (struct replay_var*)rbtree_search(store, &k);
655 /** do macro variable */
657 do_macro_variable(rbtree_type* store, char* buf, size_t remain)
659 struct replay_var* v;
664 return NULL; /* no variable name after $ */
665 while(*at && (isalnum((unsigned char)*at) || *at=='_')) {
668 /* terminator, we are working in macro_expand() buffer */
671 v = macro_getvar(store, name);
675 log_err("variable is not defined: $%s", name);
676 return NULL; /* variable undefined is error for now */
679 /* insert the variable contents */
680 if(!do_buf_insert(buf, remain, at, v->value))
682 return buf; /* and expand the variable contents */
685 /** do ctime macro on argument */
687 do_macro_ctime(char* arg)
690 time_t tt = (time_t)atoi(arg);
691 if(tt == 0 && strcmp(arg, "0") != 0) {
692 log_err("macro ctime: expected number, not: %s", arg);
696 if(buf[0]) buf[strlen(buf)-1]=0; /* remove trailing newline */
700 /** perform arithmetic operator */
702 perform_arith(double x, char op, double y, double* res)
725 /** do macro arithmetic on two numbers and operand */
727 do_macro_arith(char* orig, size_t remain, char** arithstart)
734 /* not yet done? we want number operand number expanded first. */
736 /* remember start pos of expr, skip the first number */
739 while(*at && (isdigit((unsigned char)*at) || *at == '.'))
743 /* move back to start */
744 remain += (size_t)(orig - *arithstart);
748 if(sscanf(at, " %lf %c %lf%n", &x, &operator, &y, &skip) != 3) {
750 return do_macro_arith(orig, remain, arithstart);
752 if(isdigit((unsigned char)operator)) {
754 return at+skip; /* do nothing, but setup for later number */
757 /* calculate result */
758 if(!perform_arith(x, operator, y, &result)) {
759 log_err("unknown operator: %s", at);
763 /* put result back in buffer */
764 snprintf(buf, sizeof(buf), "%.12g", result);
765 if(!do_buf_insert(at, remain, at+skip, buf))
768 /* the result can be part of another expression, restart that */
773 /** Do range macro on expanded buffer */
775 do_macro_range(char* buf)
778 if(sscanf(buf, " %lf %lf %lf", &x, &y, &z) != 3) {
779 log_err("range func requires 3 args: %s", buf);
782 if(x <= y && y <= z) {
784 snprintf(res, sizeof(res), "%.24g", y);
787 fatal_exit("value %.24g not in range [%.24g, %.24g]", y, x, z);
792 macro_expand(rbtree_type* store, struct replay_runtime* runtime, char** text)
796 size_t len = macro_length(at);
798 char* arithstart = NULL;
799 if(len >= sizeof(buf))
800 return NULL; /* too long */
802 (void)strlcpy(buf, at, len+1-1); /* do not copy last '}' character */
805 /* check for functions */
806 if(strcmp(buf, "time") == 0) {
808 snprintf(buf, sizeof(buf), ARG_LL "d", (long long)runtime->now_secs);
810 snprintf(buf, sizeof(buf), ARG_LL "d", (long long)0);
813 } else if(strcmp(buf, "timeout") == 0) {
816 struct fake_timer* t = first_timer(runtime);
817 if(t && (time_t)t->tv.tv_sec >= runtime->now_secs)
818 res = (time_t)t->tv.tv_sec - runtime->now_secs;
820 snprintf(buf, sizeof(buf), ARG_LL "d", (long long)res);
823 } else if(strncmp(buf, "ctime ", 6) == 0 ||
824 strncmp(buf, "ctime\t", 6) == 0) {
827 } else if(strncmp(buf, "range ", 6) == 0 ||
828 strncmp(buf, "range\t", 6) == 0) {
833 /* actual macro text expansion */
835 size_t remain = sizeof(buf)-strlen(buf);
836 if(strncmp(at, "${", 2) == 0) {
837 at = do_macro_recursion(store, runtime, at, remain);
838 } else if(*at == '$') {
839 at = do_macro_variable(store, at, remain);
840 } else if(isdigit((unsigned char)*at)) {
841 at = do_macro_arith(at, remain, &arithstart);
843 /* copy until whitespace or operator */
844 if(*at && (isalnum((unsigned char)*at) || *at=='_')) {
846 while(*at && (isalnum((unsigned char)*at) || *at=='_'))
850 if(!at) return NULL; /* failure */
854 /* post process functions, buf has the argument(s) */
855 if(strncmp(buf, "ctime", 5) == 0) {
856 return do_macro_ctime(buf+6);
857 } else if(strncmp(buf, "range", 5) == 0) {
858 return do_macro_range(buf+6);
865 macro_process(rbtree_type* store, struct replay_runtime* runtime, char* text)
870 if(!strstr(text, "${"))
871 return strdup(text); /* no macros */
873 buf[sizeof(buf)-1]=0;
874 while( (next=strstr(at, "${")) ) {
875 /* copy text before next macro */
876 if((size_t)(next-at) >= sizeof(buf)-strlen(buf))
877 return NULL; /* string too long */
878 (void)strlcpy(buf+strlen(buf), at, (size_t)(next-at+1));
879 /* process the macro itself */
881 expand = macro_expand(store, runtime, &next);
882 if(!expand) return NULL; /* expansion failed */
883 (void)strlcpy(buf+strlen(buf), expand, sizeof(buf)-strlen(buf));
887 /* copy remainder fixed text */
888 (void)strlcpy(buf+strlen(buf), at, sizeof(buf)-strlen(buf));
893 macro_lookup(rbtree_type* store, char* name)
895 struct replay_var* x = macro_getvar(store, name);
896 if(!x) return strdup("");
897 return strdup(x->value);
900 void macro_print_debug(rbtree_type* store)
902 struct replay_var* x;
903 RBTREE_FOR(x, struct replay_var*, store) {
904 log_info("%s = %s", x->name, x->value);
909 macro_assign(rbtree_type* store, char* name, char* value)
911 struct replay_var* x = macro_getvar(store, name);
915 x = (struct replay_var*)malloc(sizeof(*x));
918 x->name = strdup(name);
923 (void)rbtree_insert(store, &x->node);
925 x->value = strdup(value);
926 return x->value != NULL;
929 /* testbound assert function for selftest. counts the number of tests */
930 #define tb_assert(x) \
931 do { if(!(x)) fatal_exit("%s:%d: %s: assertion %s failed", \
932 __FILE__, __LINE__, __func__, #x); \
936 void testbound_selftest(void)
938 /* test the macro store */
939 rbtree_type* store = macro_store_create();
945 v = macro_lookup(store, "bla");
946 tb_assert(strcmp(v, "") == 0);
949 v = macro_lookup(store, "vlerk");
950 tb_assert(strcmp(v, "") == 0);
953 r = macro_assign(store, "bla", "waarde1");
956 v = macro_lookup(store, "vlerk");
957 tb_assert(strcmp(v, "") == 0);
960 v = macro_lookup(store, "bla");
961 tb_assert(strcmp(v, "waarde1") == 0);
964 r = macro_assign(store, "vlerk", "kanteel");
967 v = macro_lookup(store, "bla");
968 tb_assert(strcmp(v, "waarde1") == 0);
971 v = macro_lookup(store, "vlerk");
972 tb_assert(strcmp(v, "kanteel") == 0);
975 r = macro_assign(store, "bla", "ww");
978 v = macro_lookup(store, "bla");
979 tb_assert(strcmp(v, "ww") == 0);
982 tb_assert( macro_length("}") == 1);
983 tb_assert( macro_length("blabla}") == 7);
984 tb_assert( macro_length("bla${zoink}bla}") == 7+8);
985 tb_assert( macro_length("bla${zoink}${bla}bla}") == 7+8+6);
987 v = macro_process(store, NULL, "");
988 tb_assert( v && strcmp(v, "") == 0);
991 v = macro_process(store, NULL, "${}");
992 tb_assert( v && strcmp(v, "") == 0);
995 v = macro_process(store, NULL, "blabla ${} dinges");
996 tb_assert( v && strcmp(v, "blabla dinges") == 0);
999 v = macro_process(store, NULL, "1${$bla}2${$bla}3");
1000 tb_assert( v && strcmp(v, "1ww2ww3") == 0);
1003 v = macro_process(store, NULL, "it is ${ctime 123456}");
1004 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0);
1007 r = macro_assign(store, "t1", "123456");
1009 v = macro_process(store, NULL, "it is ${ctime ${$t1}}");
1010 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0);
1013 v = macro_process(store, NULL, "it is ${ctime $t1}");
1014 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0);
1017 r = macro_assign(store, "x", "1");
1019 r = macro_assign(store, "y", "2");
1021 v = macro_process(store, NULL, "${$x + $x}");
1022 tb_assert( v && strcmp(v, "2") == 0);
1024 v = macro_process(store, NULL, "${$x - $x}");
1025 tb_assert( v && strcmp(v, "0") == 0);
1027 v = macro_process(store, NULL, "${$y * $y}");
1028 tb_assert( v && strcmp(v, "4") == 0);
1030 v = macro_process(store, NULL, "${32 / $y + $x + $y}");
1031 tb_assert( v && strcmp(v, "19") == 0);
1034 v = macro_process(store, NULL, "${32 / ${$y+$y} + ${${100*3}/3}}");
1035 tb_assert( v && strcmp(v, "108") == 0);
1038 v = macro_process(store, NULL, "${1 2 33 2 1}");
1039 tb_assert( v && strcmp(v, "1 2 33 2 1") == 0);
1042 v = macro_process(store, NULL, "${123 3 + 5}");
1043 tb_assert( v && strcmp(v, "123 8") == 0);
1046 v = macro_process(store, NULL, "${123 glug 3 + 5}");
1047 tb_assert( v && strcmp(v, "123 glug 8") == 0);
1050 macro_store_delete(store);
1051 printf("selftest successful (%d checks).\n", num_asserts);