1 /* Copyright (c) 2008 The NetBSD Foundation, Inc.
2 * All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. 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 *
13 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
14 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
15 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
20 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
22 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
24 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
25
26 #if defined(HAVE_CONFIG_H)
27 #include "config.h"
28 #endif
29
30 #include <ctype.h>
31 #include <stdarg.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36
37 #include "atf-c/detail/dynstr.h"
38 #include "atf-c/detail/env.h"
39 #include "atf-c/detail/fs.h"
40 #include "atf-c/detail/map.h"
41 #include "atf-c/detail/sanity.h"
42 #include "atf-c/error.h"
43 #include "atf-c/tc.h"
44 #include "atf-c/tp.h"
45 #include "atf-c/utils.h"
46
47 #if defined(HAVE_GNU_GETOPT)
48 # define GETOPT_POSIX "+"
49 #else
50 # define GETOPT_POSIX ""
51 #endif
52
53 static const char *progname = NULL;
54
55 /* This prototype is provided by macros.h during instantiation of the test
56 * program, so it can be kept private. Don't know if that's the best idea
57 * though. */
58 int atf_tp_main(int, char **, atf_error_t (*)(atf_tp_t *));
59
60 enum tc_part {
61 BODY,
62 CLEANUP,
63 };
64
65 /* ---------------------------------------------------------------------
66 * The "usage" and "user" error types.
67 * --------------------------------------------------------------------- */
68
69 #define FREE_FORM_ERROR(name) \
70 struct name ## _error_data { \
71 char m_what[2048]; \
72 }; \
73 \
74 static \
75 void \
76 name ## _format(const atf_error_t err, char *buf, size_t buflen) \
77 { \
78 const struct name ## _error_data *data; \
79 \
80 PRE(atf_error_is(err, #name)); \
81 \
82 data = atf_error_data(err); \
83 snprintf(buf, buflen, "%s", data->m_what); \
84 } \
85 \
86 static \
87 atf_error_t \
88 name ## _error(const char *fmt, ...) \
89 { \
90 atf_error_t err; \
91 struct name ## _error_data data; \
92 va_list ap; \
93 \
94 va_start(ap, fmt); \
95 vsnprintf(data.m_what, sizeof(data.m_what), fmt, ap); \
96 va_end(ap); \
97 \
98 err = atf_error_new(#name, &data, sizeof(data), name ## _format); \
99 \
100 return err; \
101 }
102
103 FREE_FORM_ERROR(usage);
104 FREE_FORM_ERROR(user);
105
106 /* ---------------------------------------------------------------------
107 * Printing functions.
108 * --------------------------------------------------------------------- */
109
110 static
111 void
print_error(const atf_error_t err)112 print_error(const atf_error_t err)
113 {
114 char buf[4096];
115
116 PRE(atf_is_error(err));
117
118 atf_error_format(err, buf, sizeof(buf));
119 fprintf(stderr, "%s: ERROR: %s\n", progname, buf);
120
121 if (atf_error_is(err, "usage"))
122 fprintf(stderr, "%s: See atf-test-program(1) for usage details.\n",
123 progname);
124 }
125
126 static
127 void
print_warning(const char * message)128 print_warning(const char *message)
129 {
130 fprintf(stderr, "%s: WARNING: %s\n", progname, message);
131 }
132
133 /* ---------------------------------------------------------------------
134 * Options handling.
135 * --------------------------------------------------------------------- */
136
137 struct params {
138 bool m_do_list;
139 atf_fs_path_t m_srcdir;
140 char *m_tcname;
141 enum tc_part m_tcpart;
142 atf_fs_path_t m_resfile;
143 atf_map_t m_config;
144 };
145
146 static
147 atf_error_t
argv0_to_dir(const char * argv0,atf_fs_path_t * dir)148 argv0_to_dir(const char *argv0, atf_fs_path_t *dir)
149 {
150 atf_error_t err;
151 atf_fs_path_t temp;
152
153 err = atf_fs_path_init_fmt(&temp, "%s", argv0);
154 if (atf_is_error(err))
155 goto out;
156
157 err = atf_fs_path_branch_path(&temp, dir);
158
159 atf_fs_path_fini(&temp);
160 out:
161 return err;
162 }
163
164 static
165 atf_error_t
params_init(struct params * p,const char * argv0)166 params_init(struct params *p, const char *argv0)
167 {
168 atf_error_t err;
169
170 p->m_do_list = false;
171 p->m_tcname = NULL;
172 p->m_tcpart = BODY;
173
174 err = argv0_to_dir(argv0, &p->m_srcdir);
175 if (atf_is_error(err))
176 return err;
177
178 err = atf_fs_path_init_fmt(&p->m_resfile, "/dev/stdout");
179 if (atf_is_error(err)) {
180 atf_fs_path_fini(&p->m_srcdir);
181 return err;
182 }
183
184 err = atf_map_init(&p->m_config);
185 if (atf_is_error(err)) {
186 atf_fs_path_fini(&p->m_resfile);
187 atf_fs_path_fini(&p->m_srcdir);
188 return err;
189 }
190
191 return err;
192 }
193
194 static
195 void
params_fini(struct params * p)196 params_fini(struct params *p)
197 {
198 atf_map_fini(&p->m_config);
199 atf_fs_path_fini(&p->m_resfile);
200 atf_fs_path_fini(&p->m_srcdir);
201 if (p->m_tcname != NULL)
202 free(p->m_tcname);
203 }
204
205 static
206 atf_error_t
parse_vflag(char * arg,atf_map_t * config)207 parse_vflag(char *arg, atf_map_t *config)
208 {
209 atf_error_t err;
210 char *split;
211
212 split = strchr(arg, '=');
213 if (split == NULL) {
214 err = usage_error("-v requires an argument of the form var=value");
215 goto out;
216 }
217
218 *split = '\0';
219 split++;
220
221 err = atf_map_insert(config, arg, split, false);
222
223 out:
224 return err;
225 }
226
227 static
228 atf_error_t
replace_path_param(atf_fs_path_t * param,const char * value)229 replace_path_param(atf_fs_path_t *param, const char *value)
230 {
231 atf_error_t err;
232 atf_fs_path_t temp;
233
234 err = atf_fs_path_init_fmt(&temp, "%s", value);
235 if (!atf_is_error(err)) {
236 atf_fs_path_fini(param);
237 *param = temp;
238 }
239
240 return err;
241 }
242
243 /* ---------------------------------------------------------------------
244 * Test case listing.
245 * --------------------------------------------------------------------- */
246
247 static
248 void
list_tcs(const atf_tp_t * tp)249 list_tcs(const atf_tp_t *tp)
250 {
251 const atf_tc_t *const *tcs;
252 const atf_tc_t *const *tcsptr;
253
254 printf("Content-Type: application/X-atf-tp; version=\"1\"\n\n");
255
256 tcs = atf_tp_get_tcs(tp);
257 INV(tcs != NULL); /* Should be checked. */
258 for (tcsptr = tcs; *tcsptr != NULL; tcsptr++) {
259 const atf_tc_t *tc = *tcsptr;
260 char **vars = atf_tc_get_md_vars(tc);
261 char **ptr;
262
263 INV(vars != NULL); /* Should be checked. */
264
265 if (tcsptr != tcs) /* Not first. */
266 printf("\n");
267
268 for (ptr = vars; *ptr != NULL; ptr += 2) {
269 if (strcmp(*ptr, "ident") == 0) {
270 printf("ident: %s\n", *(ptr + 1));
271 break;
272 }
273 }
274
275 for (ptr = vars; *ptr != NULL; ptr += 2) {
276 if (strcmp(*ptr, "ident") != 0) {
277 printf("%s: %s\n", *ptr, *(ptr + 1));
278 }
279 }
280
281 atf_utils_free_charpp(vars);
282 }
283 }
284
285 /* ---------------------------------------------------------------------
286 * Main.
287 * --------------------------------------------------------------------- */
288
289 static
290 atf_error_t
handle_tcarg(const char * tcarg,char ** tcname,enum tc_part * tcpart)291 handle_tcarg(const char *tcarg, char **tcname, enum tc_part *tcpart)
292 {
293 atf_error_t err;
294
295 err = atf_no_error();
296
297 *tcname = strdup(tcarg);
298 if (*tcname == NULL) {
299 err = atf_no_memory_error();
300 goto out;
301 }
302
303 char *delim = strchr(*tcname, ':');
304 if (delim != NULL) {
305 *delim = '\0';
306
307 delim++;
308 if (strcmp(delim, "body") == 0) {
309 *tcpart = BODY;
310 } else if (strcmp(delim, "cleanup") == 0) {
311 *tcpart = CLEANUP;
312 } else {
313 err = usage_error("Invalid test case part `%s'", delim);
314 goto out;
315 }
316 }
317
318 out:
319 return err;
320 }
321
322 static
323 atf_error_t
process_params(int argc,char ** argv,struct params * p)324 process_params(int argc, char **argv, struct params *p)
325 {
326 atf_error_t err;
327 int ch;
328 int old_opterr;
329
330 err = params_init(p, argv[0]);
331 if (atf_is_error(err))
332 goto out;
333
334 old_opterr = opterr;
335 opterr = 0;
336 while (!atf_is_error(err) &&
337 (ch = getopt(argc, argv, GETOPT_POSIX ":lr:s:v:")) != -1) {
338 switch (ch) {
339 case 'l':
340 p->m_do_list = true;
341 break;
342
343 case 'r':
344 err = replace_path_param(&p->m_resfile, optarg);
345 break;
346
347 case 's':
348 err = replace_path_param(&p->m_srcdir, optarg);
349 break;
350
351 case 'v':
352 err = parse_vflag(optarg, &p->m_config);
353 break;
354
355 case ':':
356 err = usage_error("Option -%c requires an argument.", optopt);
357 break;
358
359 case '?':
360 default:
361 err = usage_error("Unknown option -%c.", optopt);
362 }
363 }
364 argc -= optind;
365 argv += optind;
366
367 /* Clear getopt state just in case the test wants to use it. */
368 opterr = old_opterr;
369 optind = 1;
370 #if defined(HAVE_OPTRESET)
371 optreset = 1;
372 #endif
373
374 if (!atf_is_error(err)) {
375 if (p->m_do_list) {
376 if (argc > 0)
377 err = usage_error("Cannot provide test case names with -l");
378 } else {
379 if (argc == 0)
380 err = usage_error("Must provide a test case name");
381 else if (argc == 1)
382 err = handle_tcarg(argv[0], &p->m_tcname, &p->m_tcpart);
383 else if (argc > 1) {
384 err = usage_error("Cannot provide more than one test case "
385 "name");
386 }
387 }
388 }
389
390 if (atf_is_error(err))
391 params_fini(p);
392
393 out:
394 return err;
395 }
396
397 static
398 atf_error_t
srcdir_strip_libtool(atf_fs_path_t * srcdir)399 srcdir_strip_libtool(atf_fs_path_t *srcdir)
400 {
401 atf_error_t err;
402 atf_fs_path_t parent;
403
404 err = atf_fs_path_branch_path(srcdir, &parent);
405 if (atf_is_error(err))
406 goto out;
407
408 atf_fs_path_fini(srcdir);
409 *srcdir = parent;
410
411 INV(!atf_is_error(err));
412 out:
413 return err;
414 }
415
416 static
417 atf_error_t
handle_srcdir(struct params * p)418 handle_srcdir(struct params *p)
419 {
420 atf_error_t err;
421 atf_dynstr_t leafname;
422 atf_fs_path_t exe, srcdir;
423 bool b;
424
425 err = atf_fs_path_copy(&srcdir, &p->m_srcdir);
426 if (atf_is_error(err))
427 goto out;
428
429 if (!atf_fs_path_is_absolute(&srcdir)) {
430 atf_fs_path_t srcdirabs;
431
432 err = atf_fs_path_to_absolute(&srcdir, &srcdirabs);
433 if (atf_is_error(err))
434 goto out_srcdir;
435
436 atf_fs_path_fini(&srcdir);
437 srcdir = srcdirabs;
438 }
439
440 err = atf_fs_path_leaf_name(&srcdir, &leafname);
441 if (atf_is_error(err))
442 goto out_srcdir;
443 else {
444 const bool libs = atf_equal_dynstr_cstring(&leafname, ".libs");
445 atf_dynstr_fini(&leafname);
446
447 if (libs) {
448 err = srcdir_strip_libtool(&srcdir);
449 if (atf_is_error(err))
450 goto out;
451 }
452 }
453
454 err = atf_fs_path_copy(&exe, &srcdir);
455 if (atf_is_error(err))
456 goto out_srcdir;
457
458 err = atf_fs_path_append_fmt(&exe, "%s", progname);
459 if (atf_is_error(err))
460 goto out_exe;
461
462 err = atf_fs_exists(&exe, &b);
463 if (!atf_is_error(err)) {
464 if (b) {
465 err = atf_map_insert(&p->m_config, "srcdir",
466 strdup(atf_fs_path_cstring(&srcdir)), true);
467 } else {
468 err = user_error("Cannot find the test program in the source "
469 "directory `%s'", atf_fs_path_cstring(&srcdir));
470 }
471 }
472
473 out_exe:
474 atf_fs_path_fini(&exe);
475 out_srcdir:
476 atf_fs_path_fini(&srcdir);
477 out:
478 return err;
479 }
480
481 static
482 atf_error_t
run_tc(const atf_tp_t * tp,struct params * p,int * exitcode)483 run_tc(const atf_tp_t *tp, struct params *p, int *exitcode)
484 {
485 atf_error_t err;
486
487 err = atf_no_error();
488
489 if (!atf_tp_has_tc(tp, p->m_tcname)) {
490 err = usage_error("Unknown test case `%s'", p->m_tcname);
491 goto out;
492 }
493
494 if (!atf_env_has("__RUNNING_INSIDE_ATF_RUN") || strcmp(atf_env_get(
495 "__RUNNING_INSIDE_ATF_RUN"), "internal-yes-value") != 0)
496 {
497 print_warning("Running test cases outside of kyua(1) is unsupported");
498 print_warning("No isolation nor timeout control is being applied; you "
499 "may get unexpected failures; see atf-test-case(4)");
500 }
501
502 switch (p->m_tcpart) {
503 case BODY:
504 err = atf_tp_run(tp, p->m_tcname, atf_fs_path_cstring(&p->m_resfile));
505 if (atf_is_error(err)) {
506 /* TODO: Handle error */
507 *exitcode = EXIT_FAILURE;
508 atf_error_free(err);
509 } else {
510 *exitcode = EXIT_SUCCESS;
511 }
512
513 break;
514
515 case CLEANUP:
516 err = atf_tp_cleanup(tp, p->m_tcname);
517 if (atf_is_error(err)) {
518 /* TODO: Handle error */
519 *exitcode = EXIT_FAILURE;
520 atf_error_free(err);
521 } else {
522 *exitcode = EXIT_SUCCESS;
523 }
524
525 break;
526
527 default:
528 UNREACHABLE;
529 }
530
531 INV(!atf_is_error(err));
532 out:
533 return err;
534 }
535
536 static
537 atf_error_t
controlled_main(int argc,char ** argv,atf_error_t (* add_tcs_hook)(atf_tp_t *),int * exitcode)538 controlled_main(int argc, char **argv,
539 atf_error_t (*add_tcs_hook)(atf_tp_t *),
540 int *exitcode)
541 {
542 atf_error_t err;
543 struct params p;
544 atf_tp_t tp;
545 char **raw_config;
546
547 err = process_params(argc, argv, &p);
548 if (atf_is_error(err))
549 goto out;
550
551 err = handle_srcdir(&p);
552 if (atf_is_error(err))
553 goto out_p;
554
555 raw_config = atf_map_to_charpp(&p.m_config);
556 if (raw_config == NULL) {
557 err = atf_no_memory_error();
558 goto out_p;
559 }
560 err = atf_tp_init(&tp, (const char* const*)raw_config);
561 atf_utils_free_charpp(raw_config);
562 if (atf_is_error(err))
563 goto out_p;
564
565 err = add_tcs_hook(&tp);
566 if (atf_is_error(err))
567 goto out_tp;
568
569 if (p.m_do_list) {
570 list_tcs(&tp);
571 INV(!atf_is_error(err));
572 *exitcode = EXIT_SUCCESS;
573 } else {
574 err = run_tc(&tp, &p, exitcode);
575 }
576
577 out_tp:
578 atf_tp_fini(&tp);
579 out_p:
580 params_fini(&p);
581 out:
582 return err;
583 }
584
585 int
atf_tp_main(int argc,char ** argv,atf_error_t (* add_tcs_hook)(atf_tp_t *))586 atf_tp_main(int argc, char **argv, atf_error_t (*add_tcs_hook)(atf_tp_t *))
587 {
588 atf_error_t err;
589 int exitcode;
590
591 progname = strrchr(argv[0], '/');
592 if (progname == NULL)
593 progname = argv[0];
594 else
595 progname++;
596
597 /* Libtool workaround: if running from within the source tree (binaries
598 * that are not installed yet), skip the "lt-" prefix added to files in
599 * the ".libs" directory to show the real (not temporary) name. */
600 if (strncmp(progname, "lt-", 3) == 0)
601 progname += 3;
602
603 exitcode = EXIT_FAILURE; /* Silence GCC warning. */
604 err = controlled_main(argc, argv, add_tcs_hook, &exitcode);
605 if (atf_is_error(err)) {
606 print_error(err);
607 atf_error_free(err);
608 exitcode = EXIT_FAILURE;
609 }
610
611 return exitcode;
612 }
613