tp_main.c revision 275988
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
53static 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. */
58int atf_tp_main(int, char **, atf_error_t (*)(atf_tp_t *));
59
60enum 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
103FREE_FORM_ERROR(usage);
104FREE_FORM_ERROR(user);
105
106/* ---------------------------------------------------------------------
107 * Printing functions.
108 * --------------------------------------------------------------------- */
109
110static
111void
112print_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
126static
127void
128print_warning(const char *message)
129{
130    fprintf(stderr, "%s: WARNING: %s\n", progname, message);
131}
132
133/* ---------------------------------------------------------------------
134 * Options handling.
135 * --------------------------------------------------------------------- */
136
137struct 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
146static
147atf_error_t
148argv0_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);
160out:
161    return err;
162}
163
164static
165atf_error_t
166params_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
194static
195void
196params_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
205static
206atf_error_t
207parse_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
223out:
224    return err;
225}
226
227static
228atf_error_t
229replace_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
247static
248void
249list_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
289static
290atf_error_t
291handle_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
318out:
319    return err;
320}
321
322static
323atf_error_t
324process_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
393out:
394    return err;
395}
396
397static
398atf_error_t
399srcdir_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));
412out:
413    return err;
414}
415
416static
417atf_error_t
418handle_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
473out_exe:
474    atf_fs_path_fini(&exe);
475out_srcdir:
476    atf_fs_path_fini(&srcdir);
477out:
478    return err;
479}
480
481static
482atf_error_t
483run_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));
532out:
533    return err;
534}
535
536static
537atf_error_t
538controlled_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
577out_tp:
578    atf_tp_fini(&tp);
579out_p:
580    params_fini(&p);
581out:
582    return err;
583}
584
585int
586atf_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