api documentation for ec_parse
[protos/libecoli.git] / test / test.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright 2016, Olivier MATZ <zer0@droids-corp.org>
3  */
4
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <assert.h>
8 #include <getopt.h>
9 #include <limits.h>
10 #include <execinfo.h>
11 #include <errno.h>
12
13 #include <ecoli_init.h>
14 #include <ecoli_log.h>
15 #include <ecoli_test.h>
16 #include <ecoli_malloc.h>
17
18 /* LCOV_EXCL_START */
19 EC_LOG_TYPE_REGISTER(main);
20
21 #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / \
22                 ((size_t)(!(sizeof(x) % sizeof(0[x])))))
23
24 static int log_level = EC_LOG_INFO;
25 static int alloc_fail_proba = 0;
26 static int seed = 0;
27 static size_t alloc_success = 0;
28
29 static const char ec_short_options[] =
30         "h"  /* help */
31         "l:" /* log-level */
32         "r:" /* random-alloc-fail */
33         "s:" /* seed */
34         ;
35
36 #define EC_OPT_HELP "help"
37 #define EC_OPT_LOG_LEVEL "log-level"
38 #define EC_OPT_RANDOM_ALLOC_FAIL "random-alloc-fail"
39 #define EC_OPT_SEED "seed"
40
41 static const struct option ec_long_options[] = {
42         {EC_OPT_HELP, 0, NULL, 'h'},
43         {EC_OPT_LOG_LEVEL, 1, NULL, 'l'},
44         {EC_OPT_RANDOM_ALLOC_FAIL, 1, NULL, 'r'},
45         {EC_OPT_SEED, 1, NULL, 's'},
46         {NULL, 0, NULL, 0}
47 };
48
49 static void usage(const char *prgname)
50 {
51         fprintf(stderr, "%s [options] [test1 test2 test3...]\n"
52                 "  -h\n"
53                 "  --"EC_OPT_HELP"\n"
54                 "      Show this help.\n"
55                 "  -l <level>\n"
56                 "  --"EC_OPT_LOG_LEVEL"=<level>\n"
57                 "      Set log level (0 = no log, 7 = verbose).\n"
58                 "  -r <probability>\n"
59                 "  --"EC_OPT_RANDOM_ALLOC_FAIL"=<probability>\n"
60                 "      Cause malloc to fail randomly. This helps to debug\n"
61                 "      leaks or crashes in error cases. The probability is\n"
62                 "      between 0 and 100.\n"
63                 "  -s <seed>\n"
64                 "  --seed=<seed>\n"
65                 "      Seeds the random number generator. Default is 0.\n"
66                 , prgname);
67 }
68
69 static int
70 parse_int(const char *s, int min, int max, int *ret, unsigned int base)
71 {
72         char *end = NULL;
73         long long n;
74
75         n = strtoll(s, &end, base);
76         if ((s[0] == '\0') || (end == NULL) || (*end != '\0'))
77                 return -1;
78         if (n < min)
79                 return -1;
80         if (n > max)
81                 return -1;
82
83         *ret = n;
84         return 0;
85 }
86
87 static int parse_args(int argc, char **argv)
88 {
89         int ret, opt;
90
91         while ((opt = getopt_long(argc, argv, ec_short_options,
92                                 ec_long_options, NULL)) != EOF) {
93
94                 switch (opt) {
95                 case 'h': /* help */
96                         usage(argv[0]);
97                         exit(0);
98
99                 case 'l': /* log-level */
100                         if (parse_int(optarg, EC_LOG_EMERG,
101                                         EC_LOG_DEBUG, &log_level, 10) < 0) {
102                                 printf("Invalid log value\n");
103                                 usage(argv[0]);
104                                 exit(1);
105                         }
106                         break;
107
108                 case 'r': /* random-alloc-fail */
109                         if (parse_int(optarg, 0, 100, &alloc_fail_proba,
110                                         10) < 0) {
111                                 printf("Invalid probability value\n");
112                                 usage(argv[0]);
113                                 exit(1);
114                         }
115                         break;
116
117                 case 's': /* seed */
118                         if (parse_int(optarg, 0, INT_MAX, &seed, 10) < 0) {
119                                 printf("Invalid seed value\n");
120                                 usage(argv[0]);
121                                 exit(1);
122                         }
123                         break;
124
125                 default:
126                         usage(argv[0]);
127                         return -1;
128                 }
129         }
130
131         ret = optind - 1;
132         optind = 1;
133
134         return ret;
135 }
136
137 TAILQ_HEAD(debug_alloc_hdr_list, debug_alloc_hdr);
138 static struct debug_alloc_hdr_list debug_alloc_hdr_list =
139         TAILQ_HEAD_INITIALIZER(debug_alloc_hdr_list);
140
141 #define STACK_SZ 16
142 struct debug_alloc_hdr {
143         TAILQ_ENTRY(debug_alloc_hdr) next;
144         const char *file;
145         unsigned int seq;
146         unsigned int line;
147         size_t size;
148         void *stack[STACK_SZ];
149         int stacklen;
150         unsigned int cookie;
151 };
152
153 struct debug_alloc_ftr {
154         unsigned int cookie;
155 } __attribute__((packed));
156
157 static int malloc_seq;
158
159 static void *debug_malloc(size_t size, const char *file, unsigned int line)
160 {
161         struct debug_alloc_hdr *hdr;
162         struct debug_alloc_ftr *ftr;
163         size_t new_size = size + sizeof(*hdr) + sizeof(*ftr);
164         void *ret;
165         int r = random();
166
167         if (alloc_fail_proba != 0 && (r % 100) < alloc_fail_proba)
168                 hdr = NULL;
169         else
170                 hdr = malloc(new_size);
171
172         if (hdr == NULL) {
173                 ret = NULL;
174         } else {
175                 hdr->seq = malloc_seq;
176                 hdr->file = file;
177                 hdr->line = line;
178                 hdr->size = size;
179                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
180                 hdr->cookie = 0x12345678;
181                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
182                 ret = hdr + 1;
183                 ftr = (struct debug_alloc_ftr *)(
184                         (char *)hdr + size + sizeof(*hdr));
185                 ftr->cookie = 0x87654321;
186         }
187
188         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: malloc(%zd) -> %p seq=%d\n",
189                 file, line, size, ret, malloc_seq++);
190
191         if (ret)
192                 alloc_success++;
193         return ret;
194 }
195
196 static void debug_free(void *ptr, const char *file, unsigned int line)
197 {
198         struct debug_alloc_hdr *hdr, *h;
199         struct debug_alloc_ftr *ftr;
200
201         (void)file;
202         (void)line;
203
204         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: free(%p)\n", file, line, ptr);
205
206         if (ptr == NULL)
207                 return;
208
209         hdr = (ptr - sizeof(*hdr));
210         if (hdr->cookie != 0x12345678) {
211                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad start cookie\n",
212                         file, line, ptr);
213                 abort();
214         }
215
216         ftr = (ptr + hdr->size);
217         if (ftr->cookie != 0x87654321) {
218                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad end cookie\n",
219                         file, line, ptr);
220                 abort();
221         }
222
223         TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
224                 if (h == hdr)
225                         break;
226         }
227
228         if (h == NULL) {
229                 EC_LOG(EC_LOG_ERR, "%s:%d: error: free(%p): bad ptr\n",
230                         file, line, ptr);
231                 abort();
232         }
233
234         TAILQ_REMOVE(&debug_alloc_hdr_list, hdr, next);
235         free(hdr);
236 }
237
238 static void *debug_realloc(void *ptr, size_t size, const char *file,
239         unsigned int line)
240 {
241         struct debug_alloc_hdr *hdr, *h;
242         struct debug_alloc_ftr *ftr;
243         size_t new_size = size + sizeof(*hdr) + sizeof(unsigned int);
244         void *ret;
245
246         if (ptr != NULL) {
247                 hdr =  (ptr - sizeof(*hdr));
248                 if (hdr->cookie != 0x12345678) {
249                         EC_LOG(EC_LOG_ERR,
250                                 "%s:%d: error: realloc(%p): bad start cookie\n",
251                                 file, line, ptr);
252                         abort();
253                 }
254
255                 ftr = (ptr + hdr->size);
256                 if (ftr->cookie != 0x87654321) {
257                         EC_LOG(EC_LOG_ERR,
258                                 "%s:%d: error: realloc(%p): bad end cookie\n",
259                                 file, line, ptr);
260                         abort();
261                 }
262
263                 TAILQ_FOREACH(h, &debug_alloc_hdr_list, next) {
264                         if (h == hdr)
265                                 break;
266                 }
267
268                 if (h == NULL) {
269                         EC_LOG(EC_LOG_ERR, "%s:%d: error: realloc(%p): bad ptr\n",
270                                 file, line, ptr);
271                         abort();
272                 }
273
274                 TAILQ_REMOVE(&debug_alloc_hdr_list, h, next);
275                 hdr = realloc(hdr, new_size);
276                 if (hdr == NULL) {
277                         TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, h, next);
278                         ret = NULL;
279                 } else {
280                         ret = hdr + 1;
281                 }
282         } else {
283                 hdr = realloc(NULL, new_size);
284                 if (hdr == NULL)
285                         ret = NULL;
286                 else
287                         ret = hdr + 1;
288         }
289
290         if (hdr != NULL) {
291                 hdr->seq = malloc_seq;
292                 hdr->file = file;
293                 hdr->line = line;
294                 hdr->size = size;
295                 hdr->stacklen = backtrace(hdr->stack, COUNT_OF(hdr->stack));
296                 hdr->cookie = 0x12345678;
297                 TAILQ_INSERT_TAIL(&debug_alloc_hdr_list, hdr, next);
298                 ftr = (struct debug_alloc_ftr *)(
299                         (char *)hdr + size + sizeof(*hdr));
300                 ftr->cookie = 0x87654321;
301         }
302
303         EC_LOG(EC_LOG_DEBUG, "%s:%d: info: realloc(%p, %zd) -> %p seq=%d\n",
304                 file, line, ptr, size, ret, malloc_seq++);
305
306         if (ret)
307                 alloc_success++;
308         return ret;
309 }
310
311 static int debug_alloc_dump_leaks(void)
312 {
313         struct debug_alloc_hdr *hdr;
314         int i;
315         char **buffer;
316
317         EC_LOG(EC_LOG_INFO, "%zd successful allocations\n", alloc_success);
318
319         if (TAILQ_EMPTY(&debug_alloc_hdr_list))
320                 return 0;
321
322         TAILQ_FOREACH(hdr, &debug_alloc_hdr_list, next) {
323                 EC_LOG(EC_LOG_ERR,
324                         "%s:%d: error: memory leak seq=%u size=%zd ptr=%p\n",
325                         hdr->file, hdr->line, hdr->seq, hdr->size, hdr + 1);
326                 buffer = backtrace_symbols(hdr->stack, hdr->stacklen);
327                 if (buffer == NULL) {
328                         for (i = 0; i < hdr->stacklen; i++)
329                                 EC_LOG(EC_LOG_ERR, "  %p\n", hdr->stack[i]);
330                 } else {
331                         for (i = 0; i < hdr->stacklen; i++)
332                                 EC_LOG(EC_LOG_ERR, "  %s\n",
333                                         buffer ? buffer[i] : "unknown");
334                 }
335                 free(buffer);
336         }
337
338         EC_LOG(EC_LOG_ERR,
339                 "  missing static syms, use: addr2line -f -e <prog> <addr>\n");
340
341         return -1;
342 }
343
344 static int debug_log(int type, unsigned int level, void *opaque,
345                 const char *str)
346 {
347         (void)type;
348         (void)opaque;
349
350         if (level > (unsigned int)log_level)
351                 return 0;
352
353         if (printf("%s", str) < 0)
354                 return -1;
355
356         return 0;
357 }
358
359 int main(int argc, char **argv)
360 {
361         int i, ret = 0, leaks;
362
363         ret = parse_args(argc, argv);
364         if (ret < 0)
365                 return 1;
366
367         argc -= ret;
368         argv += ret;
369
370         srandom(seed);
371
372         /* register a new malloc to track memleaks */
373         TAILQ_INIT(&debug_alloc_hdr_list);
374         if (ec_malloc_register(debug_malloc, debug_free, debug_realloc) < 0) {
375                 EC_LOG(EC_LOG_ERR, "cannot register new malloc\n");
376                 return 1;
377         }
378
379         if (ec_init() < 0) {
380                 fprintf(stderr, "cannot init ecoli: %s\n", strerror(errno));
381                 return 1;
382         }
383         ec_log_fct_register(debug_log, NULL);
384
385         ret = 0;
386         if (argc <= 1) {
387                 ret = ec_test_all();
388         } else {
389                 for (i = 1; i < argc; i++)
390                         ret |= ec_test_one(argv[i]);
391         }
392
393         ec_exit();
394
395         leaks = debug_alloc_dump_leaks();
396
397         if (alloc_fail_proba == 0 && ret != 0) {
398                 printf("tests failed\n");
399                 return 1;
400         } else if (alloc_fail_proba != 0 && leaks != 0) {
401                 printf("tests failed (memory leak)\n");
402                 return 1;
403         }
404
405         printf("\ntests ok\n");
406
407         return 0;
408 }
409 /* LCOV_EXCL_STOP */