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