xref: /freebsd/contrib/atf/atf-c/detail/tp_main.c (revision 952231086100bed7130212b5425e44ffd732913b)
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     free(p->m_tcname);
202     p->m_tcname = NULL;
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 **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     free(tcs);
284     tcs = NULL;
285 }
286 
287 /* ---------------------------------------------------------------------
288  * Main.
289  * --------------------------------------------------------------------- */
290 
291 static
292 atf_error_t
293 handle_tcarg(const char *tcarg, char **tcname_out, enum tc_part *tcpart)
294 {
295     char *delim, *tcname;
296     atf_error_t err;
297 
298     *tcname_out = NULL;
299     tcname = strdup(tcarg);
300     if (tcname == NULL) {
301         return atf_no_memory_error();
302     }
303 
304     err = atf_no_error();
305 
306     delim = strchr(tcname, ':');
307     if (delim != NULL) {
308         *delim = '\0';
309 
310         delim++;
311         if (strcmp(delim, "body") == 0) {
312             *tcpart = BODY;
313         } else if (strcmp(delim, "cleanup") == 0) {
314             *tcpart = CLEANUP;
315         } else {
316             err = usage_error("Invalid test case part `%s'", delim);
317             free(tcname);
318         }
319     }
320 
321     if (!atf_is_error(err))
322         *tcname_out = tcname;
323     return err;
324 }
325 
326 static
327 atf_error_t
328 process_params(int argc, char **argv, struct params *p)
329 {
330     atf_error_t err;
331     int ch;
332     int old_opterr;
333 
334     err = atf_no_error();
335 
336     old_opterr = opterr;
337     opterr = 0;
338     while (!atf_is_error(err) &&
339            (ch = getopt(argc, argv, GETOPT_POSIX ":lr:s:v:")) != -1) {
340         switch (ch) {
341         case 'l':
342             p->m_do_list = true;
343             break;
344 
345         case 'r':
346             err = replace_path_param(&p->m_resfile, optarg);
347             break;
348 
349         case 's':
350             err = replace_path_param(&p->m_srcdir, optarg);
351             break;
352 
353         case 'v':
354             err = parse_vflag(optarg, &p->m_config);
355             break;
356 
357         case ':':
358             err = usage_error("Option -%c requires an argument.", optopt);
359             break;
360 
361         case '?':
362         default:
363             err = usage_error("Unknown option -%c.", optopt);
364         }
365     }
366     argc -= optind;
367     argv += optind;
368 
369     /* Clear getopt state just in case the test wants to use it. */
370     opterr = old_opterr;
371     optind = 1;
372 #if defined(HAVE_OPTRESET)
373     optreset = 1;
374 #endif
375 
376     if (!atf_is_error(err)) {
377         if (p->m_do_list) {
378             if (argc > 0)
379                 err = usage_error("Cannot provide test case names with -l");
380         } else {
381             if (argc == 0)
382                 err = usage_error("Must provide a test case name");
383             else if (argc == 1)
384                 err = handle_tcarg(argv[0], &p->m_tcname, &p->m_tcpart);
385             else if (argc > 1) {
386                 err = usage_error("Cannot provide more than one test case "
387                                   "name");
388             }
389         }
390     }
391 
392     return err;
393 }
394 
395 static
396 atf_error_t
397 srcdir_strip_libtool(atf_fs_path_t *srcdir)
398 {
399     atf_error_t err;
400     atf_fs_path_t parent;
401 
402     err = atf_fs_path_branch_path(srcdir, &parent);
403     if (atf_is_error(err))
404         goto out;
405 
406     atf_fs_path_fini(srcdir);
407     *srcdir = parent;
408 
409     INV(!atf_is_error(err));
410 out:
411     return err;
412 }
413 
414 static
415 atf_error_t
416 handle_srcdir(struct params *p)
417 {
418     atf_error_t err;
419     atf_dynstr_t leafname;
420     atf_fs_path_t exe, srcdir;
421     bool b;
422 
423     err = atf_fs_path_copy(&srcdir, &p->m_srcdir);
424     if (atf_is_error(err))
425         goto out;
426 
427     if (!atf_fs_path_is_absolute(&srcdir)) {
428         atf_fs_path_t srcdirabs;
429 
430         err = atf_fs_path_to_absolute(&srcdir, &srcdirabs);
431         if (atf_is_error(err))
432             goto out_srcdir;
433 
434         atf_fs_path_fini(&srcdir);
435         srcdir = srcdirabs;
436     }
437 
438     err = atf_fs_path_leaf_name(&srcdir, &leafname);
439     if (atf_is_error(err))
440         goto out_srcdir;
441     else {
442         const bool libs = atf_equal_dynstr_cstring(&leafname, ".libs");
443         atf_dynstr_fini(&leafname);
444 
445         if (libs) {
446             err = srcdir_strip_libtool(&srcdir);
447             if (atf_is_error(err))
448                 goto out;
449         }
450     }
451 
452     err = atf_fs_path_copy(&exe, &srcdir);
453     if (atf_is_error(err))
454         goto out_srcdir;
455 
456     err = atf_fs_path_append_fmt(&exe, "%s", progname);
457     if (atf_is_error(err))
458         goto out_exe;
459 
460     err = atf_fs_exists(&exe, &b);
461     if (!atf_is_error(err)) {
462         if (b) {
463             err = atf_map_insert(&p->m_config, "srcdir",
464                                  strdup(atf_fs_path_cstring(&srcdir)), true);
465         } else {
466             err = user_error("Cannot find the test program in the source "
467                              "directory `%s'", atf_fs_path_cstring(&srcdir));
468         }
469     }
470 
471 out_exe:
472     atf_fs_path_fini(&exe);
473 out_srcdir:
474     atf_fs_path_fini(&srcdir);
475 out:
476     return err;
477 }
478 
479 static
480 atf_error_t
481 run_tc(const atf_tp_t *tp, struct params *p, int *exitcode)
482 {
483     atf_error_t err;
484 
485     if (!atf_tp_has_tc(tp, p->m_tcname))
486         return usage_error("Unknown test case `%s'", p->m_tcname);
487 
488     err = atf_no_error();
489 
490     if (!atf_env_has("__RUNNING_INSIDE_ATF_RUN") || strcmp(atf_env_get(
491         "__RUNNING_INSIDE_ATF_RUN"), "internal-yes-value") != 0)
492     {
493         print_warning("Running test cases outside of kyua(1) is unsupported");
494         print_warning("No isolation nor timeout control is being applied; you "
495                       "may get unexpected failures; see atf-test-case(4)");
496     }
497 
498     switch (p->m_tcpart) {
499     case BODY:
500         err = atf_tp_run(tp, p->m_tcname, atf_fs_path_cstring(&p->m_resfile));
501         if (atf_is_error(err)) {
502             /* TODO: Handle error */
503             *exitcode = EXIT_FAILURE;
504             atf_error_free(err);
505         } else {
506             *exitcode = EXIT_SUCCESS;
507         }
508 
509         break;
510 
511     case CLEANUP:
512         err = atf_tp_cleanup(tp, p->m_tcname);
513         if (atf_is_error(err)) {
514             /* TODO: Handle error */
515             *exitcode = EXIT_FAILURE;
516             atf_error_free(err);
517         } else {
518             *exitcode = EXIT_SUCCESS;
519         }
520 
521         break;
522 
523     default:
524         UNREACHABLE;
525     }
526 
527     INV(!atf_is_error(err));
528     return err;
529 }
530 
531 static
532 atf_error_t
533 controlled_main(int argc, char **argv,
534                 atf_error_t (*add_tcs_hook)(atf_tp_t *),
535                 int *exitcode)
536 {
537     atf_error_t err;
538     struct params p;
539     atf_tp_t tp;
540     char **raw_config;
541 
542     err = params_init(&p, argv[0]);
543     if (atf_is_error(err))
544         goto out;
545 
546     err = process_params(argc, argv, &p);
547     if (atf_is_error(err))
548         goto out_p;
549 
550     err = handle_srcdir(&p);
551     if (atf_is_error(err))
552         goto out_p;
553 
554     raw_config = atf_map_to_charpp(&p.m_config);
555     if (raw_config == NULL) {
556         err = atf_no_memory_error();
557         goto out_p;
558     }
559     err = atf_tp_init(&tp, (const char* const*)raw_config);
560     atf_utils_free_charpp(raw_config);
561     if (atf_is_error(err))
562         goto out_p;
563 
564     err = add_tcs_hook(&tp);
565     if (atf_is_error(err))
566         goto out_tp;
567 
568     if (p.m_do_list) {
569         list_tcs(&tp);
570         INV(!atf_is_error(err));
571         *exitcode = EXIT_SUCCESS;
572     } else {
573         err = run_tc(&tp, &p, exitcode);
574     }
575 
576 out_tp:
577     atf_tp_fini(&tp);
578 out_p:
579     params_fini(&p);
580 out:
581     return err;
582 }
583 
584 int
585 atf_tp_main(int argc, char **argv, atf_error_t (*add_tcs_hook)(atf_tp_t *))
586 {
587     atf_error_t err;
588     int exitcode;
589 
590     progname = strrchr(argv[0], '/');
591     if (progname == NULL)
592         progname = argv[0];
593     else
594         progname++;
595 
596     exitcode = EXIT_FAILURE; /* Silence GCC warning. */
597     err = controlled_main(argc, argv, add_tcs_hook, &exitcode);
598     if (atf_is_error(err)) {
599         print_error(err);
600         atf_error_free(err);
601         exitcode = EXIT_FAILURE;
602     }
603 
604     return exitcode;
605 }
606