8a6314b442673751eb2dc329d691713b4dd98435
[petitboot] / test / parser / utils.c
1
2 #include <assert.h>
3 #include <err.h>
4 #include <fcntl.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <ctype.h>
8 #include <sys/stat.h>
9 #include <sys/types.h>
10
11 #include <talloc/talloc.h>
12 #include <types/types.h>
13 #include <url/url.h>
14
15 #include "device-handler.h"
16 #include "parser.h"
17 #include "resource.h"
18 #include "event.h"
19 #include "platform.h"
20
21 #include "parser-test.h"
22
23 struct p_item {
24         struct list_item list;
25         struct parser *parser;
26 };
27
28 struct test_file {
29         struct discover_device  *dev;
30         enum {
31                 TEST_FILE,
32                 TEST_DIR,
33         }                       type;
34         const char              *name;
35         void                    *data;
36         int                     size;
37         struct list_item        list;
38 };
39
40 STATIC_LIST(parsers);
41
42 void __register_parser(struct parser *parser)
43 {
44         struct p_item* i = talloc(NULL, struct p_item);
45
46         i->parser = parser;
47         list_add(&parsers, &i->list);
48 }
49
50 static void __attribute__((destructor)) __cleanup_parsers(void)
51 {
52         struct p_item *item, *tmp;
53
54         list_for_each_entry_safe(&parsers, item, tmp, list)
55                 talloc_free(item);
56 }
57
58 static struct discover_device *test_create_device_simple(
59                 struct parser_test *test)
60 {
61         static int dev_idx;
62         char name[10];
63
64         sprintf(name, "__test%d", dev_idx++);
65
66         return test_create_device(test, name);
67 }
68
69 struct discover_device *test_create_device(struct parser_test *test,
70                 const char *name)
71 {
72         struct discover_device *dev;
73
74         dev = discover_device_create(test->handler, name);
75
76         dev->device->id = talloc_strdup(dev, name);
77         dev->device_path = talloc_asprintf(dev, "/dev/%s", name);
78         dev->mount_path = talloc_asprintf(dev, "/test/mount/%s", name);
79         dev->mounted = true;
80
81         return dev;
82 }
83
84 static struct discover_context *test_create_context(struct parser_test *test)
85 {
86         struct discover_context *ctx;
87
88         ctx = talloc_zero(test, struct discover_context);
89         assert(ctx);
90
91         list_init(&ctx->boot_options);
92         ctx->device = test_create_device_simple(test);
93         ctx->test_data = test;
94         device_handler_add_device(test->handler, ctx->device);
95
96         return ctx;
97 }
98
99 /* define our own test platform */
100 static bool test_platform_probe(struct platform *p __attribute__((unused)),
101                 void *ctx __attribute__((unused)))
102 {
103         return true;
104 }
105
106 struct platform test_platform = {
107         .name = "test",
108         .probe = test_platform_probe,
109 };
110
111 register_platform(test_platform);
112
113 struct parser_test *test_init(void)
114 {
115         struct parser_test *test;
116
117         test = talloc_zero(NULL, struct parser_test);
118         platform_init(NULL);
119         test->handler = device_handler_init(NULL, NULL, 0);
120         test->ctx = test_create_context(test);
121         list_init(&test->files);
122
123         return test;
124 }
125
126 void test_fini(struct parser_test *test)
127 {
128         device_handler_destroy(test->handler);
129         talloc_free(test);
130         platform_fini();
131 }
132
133 void __test_read_conf_data(struct parser_test *test,
134                 struct discover_device *dev, const char *conf_file,
135                 const char *buf, size_t len)
136 {
137         test_add_file_data(test, dev, conf_file, buf, len);
138 }
139
140 void test_read_conf_file(struct parser_test *test, const char *filename,
141                 const char *conf_file)
142 {
143         struct stat stat;
144         size_t size;
145         char *path;
146         int fd, rc;
147         char *buf;
148
149         path = talloc_asprintf(test, "%s/%s", TEST_CONF_BASE, filename);
150
151         fd = open(path, O_RDONLY);
152         if (fd < 0)
153                 err(EXIT_FAILURE, "Can't open test conf file %s\n", path);
154
155         rc = fstat(fd, &stat);
156         assert(!rc);
157         (void)rc;
158
159         size = stat.st_size;
160         buf = talloc_array(test, char, size + 1);
161
162         rc = read(fd, buf, size);
163         assert(rc == (ssize_t)size);
164
165         *(buf + size) = '\0';
166
167         close(fd);
168         talloc_free(path);
169
170         test_add_file_data(test, test->ctx->device, conf_file, buf, size);
171 }
172
173 void test_add_file_data(struct parser_test *test, struct discover_device *dev,
174                 const char *filename, const void *data, int size)
175 {
176         struct test_file *file;
177
178         file = talloc_zero(test, struct test_file);
179         file->type = TEST_FILE;
180         file->dev = dev;
181         file->name = filename;
182         file->data = talloc_memdup(test, data, size);
183         file->size = size;
184         list_add(&test->files, &file->list);
185 }
186
187 void test_add_dir(struct parser_test *test, struct discover_device *dev,
188                 const char *dirname)
189 {
190         struct test_file *file;
191
192         file = talloc_zero(test, struct test_file);
193         file->type = TEST_DIR;
194         file->dev = dev;
195         file->name = dirname;
196         list_add(&test->files, &file->list);
197 }
198
199 void test_set_event_source(struct parser_test *test)
200 {
201         test->ctx->event = talloc_zero(test->ctx, struct event);
202 }
203
204 void test_set_event_param(struct event *event, const char *name,
205                 const char *value)
206 {
207         event_set_param(event, name, value);
208 }
209
210 int parser_request_file(struct discover_context *ctx,
211                 struct discover_device *dev, const char *filename,
212                 char **buf, int *len)
213 {
214         struct parser_test *test = ctx->test_data;
215         struct test_file *file;
216         char *tmp;
217
218         list_for_each_entry(&test->files, file, list) {
219                 if (file->dev != dev)
220                         continue;
221                 if (strcmp(file->name, filename))
222                         continue;
223                 if (file->type != TEST_FILE)
224                         continue;
225
226                 /* the read_file() interface always adds a trailing null
227                  * for string-safety; do the same here */
228                 tmp = talloc_array(test, char, file->size + 1);
229                 memcpy(tmp, file->data, file->size);
230                 tmp[file->size] = '\0';
231                 *buf = tmp;
232                 *len = file->size;
233                 return 0;
234         }
235
236         return -1;
237 }
238
239 int parser_check_dir(struct discover_context *ctx,
240                 struct discover_device *dev, const char *dirname)
241 {
242         struct parser_test *test = ctx->test_data;
243         struct test_file *file;
244
245         printf("%s: %s\n", __func__, dirname);
246
247         list_for_each_entry(&test->files, file, list) {
248                 if (file->dev != dev)
249                         continue;
250                 if (strcmp(file->name, dirname))
251                         continue;
252                 return file->type == TEST_DIR ? 0 : -1;
253         }
254
255         return -1;
256 }
257
258 int parser_replace_file(struct discover_context *ctx,
259                 struct discover_device *dev, const char *filename,
260                 char *buf, int len)
261 {
262         struct parser_test *test = ctx->test_data;
263         struct test_file *f, *file;
264
265         list_for_each_entry(&test->files, f, list) {
266                 if (f->dev != dev)
267                         continue;
268                 if (strcmp(f->name, filename))
269                         continue;
270
271                 file = f;
272                 break;
273         }
274
275         if (!file) {
276                 file = talloc_zero(test, struct test_file);
277                 file->dev = dev;
278                 file->name = filename;
279                 list_add(&test->files, &file->list);
280         }
281
282         file->data = talloc_memdup(test, buf, len);
283         file->size = len;
284         return 0;
285 }
286
287 int parser_request_url(struct discover_context *ctx, struct pb_url *url,
288                 char **buf, int *len)
289 {
290         struct parser_test *test = ctx->test_data;
291         struct test_file *file;
292         char *tmp;
293
294         list_for_each_entry(&test->files, file, list) {
295                 if (file->dev)
296                         continue;
297
298                 if (strcmp(file->name, url->full))
299                         continue;
300
301                 /* the read_file() interface always adds a trailing null
302                  * for string-safety; do the same here */
303                 tmp = talloc_array(test, char, file->size + 1);
304                 memcpy(tmp, file->data, file->size);
305                 tmp[file->size] = '\0';
306                 *buf = tmp;
307                 *len = file->size;
308                 return 0;
309         }
310
311         return -1;
312 }
313
314 int test_run_parser(struct parser_test *test, const char *parser_name)
315 {
316         struct p_item* i;
317
318         list_for_each_entry(&parsers, i, list) {
319                 if (strcmp(i->parser->name, parser_name))
320                         continue;
321                 test->ctx->parser = i->parser;
322                 return i->parser->parse(test->ctx);
323         }
324
325         errx(EXIT_FAILURE, "%s: parser '%s' not found", __func__, parser_name);
326 }
327
328 bool resource_resolve(struct device_handler *handler, struct parser *parser,
329                 struct resource *resource)
330 {
331         if (!resource)
332                 return true;
333         if (resource->resolved)
334                 return true;
335
336         assert(parser);
337         assert(parser->resolve_resource);
338
339         return parser->resolve_resource(handler, resource);
340 }
341
342 void boot_option_resolve(struct device_handler *handler,
343                 struct discover_boot_option *opt)
344 {
345         resource_resolve(handler, opt->source, opt->boot_image);
346         resource_resolve(handler, opt->source, opt->initrd);
347         resource_resolve(handler, opt->source, opt->icon);
348 }
349
350 void test_hotplug_device(struct parser_test *test, struct discover_device *dev)
351 {
352         struct discover_boot_option *opt;
353
354         device_handler_add_device(test->handler, dev);
355
356         list_for_each_entry(&test->ctx->boot_options, opt, list)
357                 boot_option_resolve(test->handler, opt);
358 }
359
360 void test_remove_device(struct parser_test *test, struct discover_device *dev)
361 {
362         struct discover_boot_option *opt, *tmp;
363
364         if (dev == test->ctx->device) {
365                 list_for_each_entry_safe(&test->ctx->boot_options,
366                                 opt, tmp, list) {
367                         list_remove(&opt->list);
368                         talloc_free(opt);
369                 }
370         }
371
372         device_handler_remove(test->handler, dev);
373 }
374
375 struct discover_boot_option *get_boot_option(struct discover_context *ctx,
376                 int idx)
377 {
378         struct discover_boot_option *opt;
379         int i = 0;
380
381         list_for_each_entry(&ctx->boot_options, opt, list) {
382                 if (i++ == idx)
383                         return opt;
384         }
385
386         assert(0);
387
388         return NULL;
389 }
390
391 void __check_boot_option_count(struct discover_context *ctx, int count,
392                 const char *file, int line)
393 {
394         struct discover_boot_option *opt;
395         int defaults = 0, i = 0;
396
397         list_for_each_entry(&ctx->boot_options, opt, list) {
398                 i++;
399                 if (opt->option->is_default)
400                         defaults++;
401         }
402
403         if (defaults > 1) {
404                 fprintf(stderr, "%s:%d: parser returned multiple default "
405                                 "options\n", file, line);
406                 exit(EXIT_FAILURE);
407         }
408
409         if (i == count)
410                 return;
411
412         fprintf(stderr, "%s:%d: boot option count check failed\n", file, line);
413         fprintf(stderr, "expected %d options, got %d:\n", count, i);
414
415         i = 1;
416         list_for_each_entry(&ctx->boot_options, opt, list)
417                 fprintf(stderr, "  %2d: %s [%s]\n", i++, opt->option->name,
418                                 opt->option->id);
419
420         exit(EXIT_FAILURE);
421 }
422
423 void __check_args(struct discover_boot_option *opt, const char *args,
424                 const char *file, int line)
425 {
426         int rc;
427
428         if (!opt->option->boot_args && !args)
429                 return;
430
431         if (!opt->option->boot_args) {
432                 fprintf(stderr, "%s:%d: arg check failed\n", file, line);
433                 fprintf(stderr, "  no arguments parsed\n");
434                 fprintf(stderr, "  expected '%s'\n", args);
435                 exit(EXIT_FAILURE);
436         }
437
438         rc = strcmp(opt->option->boot_args, args);
439         if (rc) {
440                 fprintf(stderr, "%s:%d: arg check failed\n", file, line);
441                 fprintf(stderr, "  got      '%s'\n", opt->option->boot_args);
442                 fprintf(stderr, "  expected '%s'\n", args);
443                 exit(EXIT_FAILURE);
444         }
445 }
446
447 void __check_name(struct discover_boot_option *opt, const char *name,
448                 const char *file, int line)
449 {
450         int rc;
451
452         rc = strcmp(opt->option->name, name);
453         if (rc) {
454                 fprintf(stderr, "%s:%d: name check failed\n", file, line);
455                 fprintf(stderr, "  got      '%s'\n", opt->option->name);
456                 fprintf(stderr, "  expected '%s'\n", name);
457                 exit(EXIT_FAILURE);
458         }
459 }
460
461 void __check_is_default(struct discover_boot_option *opt,
462                 const char *file, int line)
463 {
464         if (opt->option->is_default)
465                 return;
466
467         fprintf(stderr, "%s:%d: default check failed\n", file, line);
468         exit(EXIT_FAILURE);
469 }
470
471 void __check_resolved_local_resource(struct resource *res,
472                 struct discover_device *dev, const char *local_path,
473                 const char *file, int line)
474 {
475         const char *exp_url, *got_url;
476
477         if (!res)
478                 errx(EXIT_FAILURE, "%s:%d: No resource", file, line);
479
480         if (!res->resolved)
481                 errx(EXIT_FAILURE, "%s:%d: Resource is not resolved",
482                                 file, line);
483
484         exp_url = talloc_asprintf(res, "file://%s%s",
485                         dev->mount_path, local_path);
486         got_url = pb_url_to_string(res->url);
487
488         if (strcmp(got_url, exp_url)) {
489                 fprintf(stderr, "%s:%d: Resource mismatch\n", file, line);
490                 fprintf(stderr, "  got      '%s'\n", got_url);
491                 fprintf(stderr, "  expected '%s'\n", exp_url);
492                 exit(EXIT_FAILURE);
493         }
494 }
495
496 void __check_resolved_url_resource(struct resource *res,
497                 const char *url, const char *file, int line)
498 {
499         char *res_url;
500
501         if (!res)
502                 errx(EXIT_FAILURE, "%s:%d: No resource", file, line);
503
504         if (!res->resolved)
505                 errx(EXIT_FAILURE, "%s:%d: Resource is not resolved",
506                                 file, line);
507
508         res_url = pb_url_to_string(res->url);
509         if (strcmp(url, res_url)) {
510                 fprintf(stderr, "%s:%d: Resource mismatch\n", file, line);
511                 fprintf(stderr, "  got      '%s'\n", res_url);
512                 fprintf(stderr, "  expected '%s'\n", url);
513                 exit(EXIT_FAILURE);
514         }
515 }
516 void __check_unresolved_resource(struct resource *res,
517                 const char *file, int line)
518 {
519         if (!res)
520                 errx(EXIT_FAILURE, "%s:%d: No resource", file, line);
521
522         if (res->resolved)
523                 errx(EXIT_FAILURE, "%s:%d: Resource is resolved", file, line);
524 }
525
526 void __check_not_present_resource(struct resource *res,
527                 const char *file, int line)
528 {
529         if (res)
530                 errx(EXIT_FAILURE, "%s:%d: Resource present", file, line);
531 }
532
533 static void dump_file_data(const void *buf, int len)
534 {
535         int i, j, hex_len = strlen("00 ");
536         const int row_len = 16;
537
538         for (i = 0; i < len; i += row_len) {
539                 char hbuf[row_len * hex_len + 1];
540                 char cbuf[row_len + strlen("|") + 1];
541
542                 for (j = 0; (j < row_len) && ((i+j) < len); j++) {
543                         char c = ((const char *)buf)[i + j];
544
545                         snprintf(hbuf + j * hex_len, hex_len + 1, "%02x ", c);
546
547                         if (!isprint(c))
548                                 c = '.';
549
550                         snprintf(cbuf + j, hex_len + 1, "%c", c);
551                 }
552
553                 strcat(cbuf, "|");
554
555                 fprintf(stderr, "%08x  %*s |%s\n", i,
556                                 0 - (int)sizeof(hbuf) + 1, hbuf, cbuf);
557         }
558 }
559
560 void __check_file_contents(struct parser_test *test,
561                 struct discover_device *dev, const char *filename,
562                 const char *buf, int len,
563                 const char *srcfile, int srcline)
564 {
565         struct test_file *f, *file = NULL;
566
567         list_for_each_entry(&test->files, f, list) {
568                 if (f->dev != dev)
569                         continue;
570                 if (strcmp(f->name, filename))
571                         continue;
572
573                 file = f;
574                 break;
575         }
576
577         if (!file)
578                 errx(EXIT_FAILURE, "%s:%d: File '%s' not found",
579                                 srcfile, srcline, filename);
580
581         if (file->size != len || memcmp(file->data, buf, len)) {
582                 fprintf(stderr, "%s:%d: File '%s' data/size mismatch\n",
583                                 srcfile, srcline, filename);
584                 fprintf(stderr, "Expected:\n");
585                 dump_file_data(buf, len);
586                 fprintf(stderr, "Got:\n");
587                 dump_file_data(file->data, file->size);
588                 exit(EXIT_FAILURE);
589         }
590 }