xref: /freebsd/contrib/atf/atf-c/detail/tp_main.c (revision a812392203d7c4c3f0db9d8a0f3391374c49c71f)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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