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 "ldns/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_t
* 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
, "ERROR")) {
327 mom
->evt_type
= repevt_error
;
328 } else if(parse_keyword(&remain
, "TRAFFIC")) {
329 mom
->evt_type
= repevt_traffic
;
330 } else if(parse_keyword(&remain
, "ASSIGN")) {
331 mom
->evt_type
= repevt_assign
;
332 read_assign_step(remain
, mom
);
333 } else if(parse_keyword(&remain
, "INFRA_RTT")) {
335 mom
->evt_type
= repevt_infra_rtt
;
336 while(isspace((unsigned char)*remain
))
339 remain
= strchr(s
, ' ');
340 if(!remain
) fatal_exit("expected three args for INFRA_RTT");
343 while(isspace((unsigned char)*remain
))
345 m
= strchr(remain
, ' ');
346 if(!m
) fatal_exit("expected three args for INFRA_RTT");
349 while(isspace((unsigned char)*m
))
351 if(!extstrtoaddr(s
, &mom
->addr
, &mom
->addrlen
))
352 fatal_exit("bad infra_rtt address %s", s
);
353 if(strlen(m
)>0 && m
[strlen(m
)-1]=='\n')
355 mom
->variable
= strdup(remain
);
356 mom
->string
= strdup(m
);
357 if(!mom
->string
) fatal_exit("out of memory");
358 if(!mom
->variable
) fatal_exit("out of memory");
360 log_err("%d: unknown event type %s", pstate
->lineno
, remain
);
364 while(isspace((unsigned char)*remain
))
366 if(parse_keyword(&remain
, "ADDRESS")) {
367 while(isspace((unsigned char)*remain
))
369 if(strlen(remain
) > 0) /* remove \n */
370 remain
[strlen(remain
)-1] = 0;
371 if(!extstrtoaddr(remain
, &mom
->addr
, &mom
->addrlen
)) {
372 log_err("line %d: could not parse ADDRESS: %s",
373 pstate
->lineno
, remain
);
378 if(parse_keyword(&remain
, "ELAPSE")) {
381 sec
= strtod(remain
, &remain
);
382 if(sec
== 0. && errno
!= 0) {
383 log_err("line %d: could not parse ELAPSE: %s (%s)",
384 pstate
->lineno
, remain
, strerror(errno
));
389 mom
->elapse
.tv_sec
= (int)sec
;
390 mom
->elapse
.tv_usec
= (int)((sec
- (double)mom
->elapse
.tv_sec
)
396 mom
->match
= read_entry(in
, name
, pstate
, 1);
406 /** makes scenario with title on rest of line */
407 static struct replay_scenario
*
408 make_scenario(char* line
)
410 struct replay_scenario
* scen
;
411 while(isspace((unsigned char)*line
))
414 log_err("scenario: no title given");
417 scen
= (struct replay_scenario
*)malloc(sizeof(struct replay_scenario
));
420 memset(scen
, 0, sizeof(*scen
));
421 scen
->title
= strdup(line
);
429 struct replay_scenario
*
430 replay_scenario_read(FILE* in
, const char* name
, int* lineno
)
432 char line
[MAX_LINE_LEN
];
434 struct replay_scenario
* scen
= NULL
;
435 struct sldns_file_parse_state pstate
;
436 line
[MAX_LINE_LEN
-1]=0;
437 memset(&pstate
, 0, sizeof(pstate
));
438 pstate
.default_ttl
= 3600;
439 pstate
.lineno
= *lineno
;
441 while(fgets(line
, MAX_LINE_LEN
-1, in
)) {
445 while(isspace((unsigned char)*parse
))
448 continue; /* empty line */
449 if(parse_keyword(&parse
, ";"))
450 continue; /* comment */
451 if(parse_keyword(&parse
, "SCENARIO_BEGIN")) {
452 scen
= make_scenario(parse
);
454 fatal_exit("%d: could not make scen", *lineno
);
458 fatal_exit("%d: expected SCENARIO", *lineno
);
459 if(parse_keyword(&parse
, "RANGE_BEGIN")) {
460 struct replay_range
* newr
= replay_range_read(parse
,
461 in
, name
, &pstate
, line
);
463 fatal_exit("%d: bad range", pstate
.lineno
);
464 *lineno
= pstate
.lineno
;
465 newr
->next_range
= scen
->range_list
;
466 scen
->range_list
= newr
;
467 } else if(parse_keyword(&parse
, "STEP")) {
468 struct replay_moment
* mom
= replay_moment_read(parse
,
471 fatal_exit("%d: bad moment", pstate
.lineno
);
472 *lineno
= pstate
.lineno
;
474 scen
->mom_last
->time_step
>= mom
->time_step
)
475 fatal_exit("%d: time goes backwards", *lineno
);
477 scen
->mom_last
->mom_next
= mom
;
478 else scen
->mom_first
= mom
;
479 scen
->mom_last
= mom
;
480 } else if(parse_keyword(&parse
, "SCENARIO_END")) {
481 struct replay_moment
*p
= scen
->mom_first
;
487 log_info("Scenario has %d steps", num
);
491 replay_scenario_delete(scen
);
496 replay_scenario_delete(struct replay_scenario
* scen
)
498 struct replay_moment
* mom
, *momn
;
499 struct replay_range
* rng
, *rngn
;
504 mom
= scen
->mom_first
;
506 momn
= mom
->mom_next
;
507 replay_moment_delete(mom
);
510 rng
= scen
->range_list
;
512 rngn
= rng
->next_range
;
513 replay_range_delete(rng
);
519 /** fetch oldest timer in list that is enabled */
520 static struct fake_timer
*
521 first_timer(struct replay_runtime
* runtime
)
523 struct fake_timer
* p
, *res
= NULL
;
524 for(p
=runtime
->timer_list
; p
; p
=p
->next
) {
529 else if(timeval_smaller(&p
->tv
, &res
->tv
))
536 replay_get_oldest_timer(struct replay_runtime
* runtime
)
538 struct fake_timer
* t
= first_timer(runtime
);
539 if(t
&& timeval_smaller(&t
->tv
, &runtime
->now_tv
))
545 replay_var_compare(const void* a
, const void* b
)
547 struct replay_var
* x
= (struct replay_var
*)a
;
548 struct replay_var
* y
= (struct replay_var
*)b
;
549 return strcmp(x
->name
, y
->name
);
553 macro_store_create(void)
555 return rbtree_create(&replay_var_compare
);
558 /** helper function to delete macro values */
560 del_macro(rbnode_t
* x
, void* ATTR_UNUSED(arg
))
562 struct replay_var
* v
= (struct replay_var
*)x
;
569 macro_store_delete(rbtree_t
* store
)
573 traverse_postorder(store
, del_macro
, NULL
);
577 /** return length of macro */
579 macro_length(char* text
)
581 /* we are after ${, looking for } */
590 } else if(text
[0] == '$' && text
[1] == '{') {
598 /** insert new stuff at start of buffer */
600 do_buf_insert(char* buf
, size_t remain
, char* after
, char* inserted
)
602 char* save
= strdup(after
);
605 if(strlen(inserted
) > remain
) {
609 len
= strlcpy(buf
, inserted
, remain
);
612 (void)strlcpy(buf
, save
, remain
);
617 /** do macro recursion */
619 do_macro_recursion(rbtree_t
* store
, struct replay_runtime
* runtime
,
620 char* at
, size_t remain
)
623 char* expand
= macro_expand(store
, runtime
, &after
);
625 return NULL
; /* expansion failed */
626 if(!do_buf_insert(at
, remain
, after
, expand
)) {
631 return at
; /* and parse over the expanded text to see if again */
634 /** get var from store */
635 static struct replay_var
*
636 macro_getvar(rbtree_t
* store
, char* name
)
641 return (struct replay_var
*)rbtree_search(store
, &k
);
644 /** do macro variable */
646 do_macro_variable(rbtree_t
* store
, char* buf
, size_t remain
)
648 struct replay_var
* v
;
653 return NULL
; /* no variable name after $ */
654 while(*at
&& (isalnum((unsigned char)*at
) || *at
=='_')) {
657 /* terminator, we are working in macro_expand() buffer */
660 v
= macro_getvar(store
, name
);
664 log_err("variable is not defined: $%s", name
);
665 return NULL
; /* variable undefined is error for now */
668 /* insert the variable contents */
669 if(!do_buf_insert(buf
, remain
, at
, v
->value
))
671 return buf
; /* and expand the variable contents */
674 /** do ctime macro on argument */
676 do_macro_ctime(char* arg
)
679 time_t tt
= (time_t)atoi(arg
);
680 if(tt
== 0 && strcmp(arg
, "0") != 0) {
681 log_err("macro ctime: expected number, not: %s", arg
);
685 if(buf
[0]) buf
[strlen(buf
)-1]=0; /* remove trailing newline */
689 /** perform arithmetic operator */
691 perform_arith(double x
, char op
, double y
, double* res
)
713 /** do macro arithmetic on two numbers and operand */
715 do_macro_arith(char* orig
, size_t remain
, char** arithstart
)
722 /* not yet done? we want number operand number expanded first. */
724 /* remember start pos of expr, skip the first number */
727 while(*at
&& (isdigit((unsigned char)*at
) || *at
== '.'))
731 /* move back to start */
732 remain
+= (size_t)(orig
- *arithstart
);
736 if(sscanf(at
, " %lf %c %lf%n", &x
, &operator, &y
, &skip
) != 3) {
738 return do_macro_arith(orig
, remain
, arithstart
);
740 if(isdigit((unsigned char)operator)) {
742 return at
+skip
; /* do nothing, but setup for later number */
745 /* calculate result */
746 if(!perform_arith(x
, operator, y
, &result
)) {
747 log_err("unknown operator: %s", at
);
751 /* put result back in buffer */
752 snprintf(buf
, sizeof(buf
), "%.12g", result
);
753 if(!do_buf_insert(at
, remain
, at
+skip
, buf
))
756 /* the result can be part of another expression, restart that */
761 /** Do range macro on expanded buffer */
763 do_macro_range(char* buf
)
766 if(sscanf(buf
, " %lf %lf %lf", &x
, &y
, &z
) != 3) {
767 log_err("range func requires 3 args: %s", buf
);
770 if(x
<= y
&& y
<= z
) {
772 snprintf(res
, sizeof(res
), "%.24g", y
);
775 fatal_exit("value %.24g not in range [%.24g, %.24g]", y
, x
, z
);
780 macro_expand(rbtree_t
* store
, struct replay_runtime
* runtime
, char** text
)
784 size_t len
= macro_length(at
);
786 char* arithstart
= NULL
;
787 if(len
>= sizeof(buf
))
788 return NULL
; /* too long */
790 (void)strlcpy(buf
, at
, len
+1-1); /* do not copy last '}' character */
793 /* check for functions */
794 if(strcmp(buf
, "time") == 0) {
795 snprintf(buf
, sizeof(buf
), ARG_LL
"d", (long long)runtime
->now_secs
);
798 } else if(strcmp(buf
, "timeout") == 0) {
800 struct fake_timer
* t
= first_timer(runtime
);
801 if(t
&& (time_t)t
->tv
.tv_sec
>= runtime
->now_secs
)
802 res
= (time_t)t
->tv
.tv_sec
- runtime
->now_secs
;
803 snprintf(buf
, sizeof(buf
), ARG_LL
"d", (long long)res
);
806 } else if(strncmp(buf
, "ctime ", 6) == 0 ||
807 strncmp(buf
, "ctime\t", 6) == 0) {
810 } else if(strncmp(buf
, "range ", 6) == 0 ||
811 strncmp(buf
, "range\t", 6) == 0) {
816 /* actual macro text expansion */
818 size_t remain
= sizeof(buf
)-strlen(buf
);
819 if(strncmp(at
, "${", 2) == 0) {
820 at
= do_macro_recursion(store
, runtime
, at
, remain
);
821 } else if(*at
== '$') {
822 at
= do_macro_variable(store
, at
, remain
);
823 } else if(isdigit((unsigned char)*at
)) {
824 at
= do_macro_arith(at
, remain
, &arithstart
);
826 /* copy until whitespace or operator */
827 if(*at
&& (isalnum((unsigned char)*at
) || *at
=='_')) {
829 while(*at
&& (isalnum((unsigned char)*at
) || *at
=='_'))
833 if(!at
) return NULL
; /* failure */
837 /* post process functions, buf has the argument(s) */
838 if(strncmp(buf
, "ctime", 5) == 0) {
839 return do_macro_ctime(buf
+6);
840 } else if(strncmp(buf
, "range", 5) == 0) {
841 return do_macro_range(buf
+6);
848 macro_process(rbtree_t
* store
, struct replay_runtime
* runtime
, char* text
)
853 if(!strstr(text
, "${"))
854 return strdup(text
); /* no macros */
856 buf
[sizeof(buf
)-1]=0;
857 while( (next
=strstr(at
, "${")) ) {
858 /* copy text before next macro */
859 if((size_t)(next
-at
) >= sizeof(buf
)-strlen(buf
))
860 return NULL
; /* string too long */
861 (void)strlcpy(buf
+strlen(buf
), at
, (size_t)(next
-at
+1));
862 /* process the macro itself */
864 expand
= macro_expand(store
, runtime
, &next
);
865 if(!expand
) return NULL
; /* expansion failed */
866 (void)strlcpy(buf
+strlen(buf
), expand
, sizeof(buf
)-strlen(buf
));
870 /* copy remainder fixed text */
871 (void)strlcpy(buf
+strlen(buf
), at
, sizeof(buf
)-strlen(buf
));
876 macro_lookup(rbtree_t
* store
, char* name
)
878 struct replay_var
* x
= macro_getvar(store
, name
);
879 if(!x
) return strdup("");
880 return strdup(x
->value
);
883 void macro_print_debug(rbtree_t
* store
)
885 struct replay_var
* x
;
886 RBTREE_FOR(x
, struct replay_var
*, store
) {
887 log_info("%s = %s", x
->name
, x
->value
);
892 macro_assign(rbtree_t
* store
, char* name
, char* value
)
894 struct replay_var
* x
= macro_getvar(store
, name
);
898 x
= (struct replay_var
*)malloc(sizeof(*x
));
901 x
->name
= strdup(name
);
906 (void)rbtree_insert(store
, &x
->node
);
908 x
->value
= strdup(value
);
909 return x
->value
!= NULL
;
912 void testbound_selftest(void)
914 /* test the macro store */
915 rbtree_t
* store
= macro_store_create();
920 v
= macro_lookup(store
, "bla");
921 log_assert(strcmp(v
, "") == 0);
924 v
= macro_lookup(store
, "vlerk");
925 log_assert(strcmp(v
, "") == 0);
928 r
= macro_assign(store
, "bla", "waarde1");
931 v
= macro_lookup(store
, "vlerk");
932 log_assert(strcmp(v
, "") == 0);
935 v
= macro_lookup(store
, "bla");
936 log_assert(strcmp(v
, "waarde1") == 0);
939 r
= macro_assign(store
, "vlerk", "kanteel");
942 v
= macro_lookup(store
, "bla");
943 log_assert(strcmp(v
, "waarde1") == 0);
946 v
= macro_lookup(store
, "vlerk");
947 log_assert(strcmp(v
, "kanteel") == 0);
950 r
= macro_assign(store
, "bla", "ww");
953 v
= macro_lookup(store
, "bla");
954 log_assert(strcmp(v
, "ww") == 0);
957 log_assert( macro_length("}") == 1);
958 log_assert( macro_length("blabla}") == 7);
959 log_assert( macro_length("bla${zoink}bla}") == 7+8);
960 log_assert( macro_length("bla${zoink}${bla}bla}") == 7+8+6);
962 v
= macro_process(store
, NULL
, "");
963 log_assert( v
&& strcmp(v
, "") == 0);
966 v
= macro_process(store
, NULL
, "${}");
967 log_assert( v
&& strcmp(v
, "") == 0);
970 v
= macro_process(store
, NULL
, "blabla ${} dinges");
971 log_assert( v
&& strcmp(v
, "blabla dinges") == 0);
974 v
= macro_process(store
, NULL
, "1${$bla}2${$bla}3");
975 log_assert( v
&& strcmp(v
, "1ww2ww3") == 0);
978 v
= macro_process(store
, NULL
, "it is ${ctime 123456}");
979 log_assert( v
&& strcmp(v
, "it is Fri Jan 2 10:17:36 1970") == 0);
982 r
= macro_assign(store
, "t1", "123456");
984 v
= macro_process(store
, NULL
, "it is ${ctime ${$t1}}");
985 log_assert( v
&& strcmp(v
, "it is Fri Jan 2 10:17:36 1970") == 0);
988 v
= macro_process(store
, NULL
, "it is ${ctime $t1}");
989 log_assert( v
&& strcmp(v
, "it is Fri Jan 2 10:17:36 1970") == 0);
992 r
= macro_assign(store
, "x", "1");
994 r
= macro_assign(store
, "y", "2");
996 v
= macro_process(store
, NULL
, "${$x + $x}");
997 log_assert( v
&& strcmp(v
, "2") == 0);
999 v
= macro_process(store
, NULL
, "${$x - $x}");
1000 log_assert( v
&& strcmp(v
, "0") == 0);
1002 v
= macro_process(store
, NULL
, "${$y * $y}");
1003 log_assert( v
&& strcmp(v
, "4") == 0);
1005 v
= macro_process(store
, NULL
, "${32 / $y + $x + $y}");
1006 log_assert( v
&& strcmp(v
, "19") == 0);
1009 v
= macro_process(store
, NULL
, "${32 / ${$y+$y} + ${${100*3}/3}}");
1010 log_assert( v
&& strcmp(v
, "108") == 0);
1013 v
= macro_process(store
, NULL
, "${1 2 33 2 1}");
1014 log_assert( v
&& strcmp(v
, "1 2 33 2 1") == 0);
1017 v
= macro_process(store
, NULL
, "${123 3 + 5}");
1018 log_assert( v
&& strcmp(v
, "123 8") == 0);
1021 v
= macro_process(store
, NULL
, "${123 glug 3 + 5}");
1022 log_assert( v
&& strcmp(v
, "123 glug 8") == 0);
1025 macro_store_delete(store
);