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